/*
 * Copyright (C) 1994-2021 Altair Engineering, Inc.
 * For more information, contact Altair at www.altair.com.
 *
 * This file is part of both the OpenPBS software ("OpenPBS")
 * and the PBS Professional ("PBS Pro") software.
 *
 * Open Source License Information:
 *
 * OpenPBS is free software. You can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * OpenPBS is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public
 * License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Commercial License Information:
 *
 * PBS Pro is commercially licensed software that shares a common core with
 * the OpenPBS software.  For a copy of the commercial license terms and
 * conditions, go to: (http://www.pbspro.com/agreement.html) or contact the
 * Altair Legal Department.
 *
 * Altair's dual-license business model allows companies, individuals, and
 * organizations to create proprietary derivative works of OpenPBS and
 * distribute them - whether embedded or bundled with other software -
 * under a commercial license agreement.
 *
 * Use of Altair's trademarks, including but not limited to "PBS™",
 * "OpenPBS®", "PBS Professional®", and "PBS Pro™" and Altair's logos is
 * subject to Altair's trademark licensing policies.
 */

/**
 * @file	mom_hook_func.c
 */
#include <pbs_config.h> /* the master config generated by configure */

#include <unistd.h>
#include <sys/param.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <ctype.h>
#include <errno.h>
#include <assert.h>

#include <memory.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "pbs_ifl.h"
#include "libpbs.h"
#include "list_link.h"
#include "work_task.h"
#include "hook.h"
#include "log.h"
#include "server_limits.h"
#include "attribute.h"
#include "credential.h"
#include "batch_request.h"
#include "job.h"
#include "svrfunc.h"
#include "pbs_python.h" /* for python interpreter */
#include <signal.h>
#include "mom_func.h"
#include "placementsets.h"
#include "resmon.h"
#include "libutil.h"
#include "pbs_nodes.h"
#include "net_connect.h"
#include "mom_hook_func.h"
#include "mom_server.h"
#include "hook.h"
#include "pbs_reliable.h"
#include "pbs_version.h"
#include "tpp.h"
#include "dis.h"
#include <openssl/sha.h>

#define RESCASSN_NCPUS "resources_assigned.ncpus"
#define RESCASSN_MEM "resources_assigned.mem"
#define RESCASSN_HOST "resources_assigned.host"
/* External functions */

/* Local Private Functions */

/* Global Data items */
static int run_exit = 0; /* run exit of child */

extern int exiting_tasks;
extern int resc_access_perm;
extern char *path_hooks;
extern char *path_hooks_workdir;
extern char *path_log;
extern char *path_spool;
extern char *mom_home;
extern char mom_host[];
extern char mom_short_name[];
extern pbs_list_head svr_execjob_begin_hooks;
extern pbs_list_head svr_execjob_prologue_hooks;
extern pbs_list_head svr_execjob_epilogue_hooks;
extern pbs_list_head svr_execjob_preterm_hooks;
extern pbs_list_head svr_execjob_launch_hooks;
extern pbs_list_head svr_execjob_end_hooks;
extern pbs_list_head svr_exechost_periodic_hooks;
extern pbs_list_head svr_exechost_startup_hooks;
extern pbs_list_head svr_execjob_attach_hooks;
extern pbs_list_head svr_execjob_resize_hooks;
extern pbs_list_head svr_execjob_abort_hooks;
extern pbs_list_head svr_execjob_postsuspend_hooks;
extern pbs_list_head svr_execjob_preresume_hooks;
extern pbs_list_head svr_hook_job_actions;
extern pbs_list_head svr_hook_vnl_actions;

extern pbs_list_head task_list_immed;
extern pbs_list_head task_list_timed;
extern pbs_list_head task_list_event;
extern pbs_list_head svr_alljobs;

extern char *msg_err_malloc;

extern time_t time_now;

extern int num_pcpus;
extern int num_acpus;
extern u_Long av_phy_mem;

extern int becomeuser(job *pjob);

extern int send_sched_recycle(char *user);

static void post_periodic_hook(struct work_task *pwt);
static void mom_process_background_hooks(struct work_task *ptask);

extern vnl_t *vnlp;
extern unsigned long hook_action_id;
extern int internal_state_update; /* flag for sending mom information update to the server */

extern int server_stream;

extern char **environ;

/**
 * @brief
 * 	Print job data into stream pointed by 'fp'.
 *
 * @param[in]	fp - stream pointer where data is flushed
 * @param[in]	pjob - pointer to job whose data is being printed out
 *
 */
static void
fprintf_job_struct(FILE *fp, job *pjob)
{
	pbs_list_head phead;
	svrattrl *psatl;
	svrattrl *ps;
	int i;

	if ((fp == NULL) || (pjob == NULL)) {
		return;
	}

	fprintf(fp, "%s.%s=%s\n", EVENT_JOB_OBJECT, "id", pjob->ji_qs.ji_jobid);

	/* Now print job attributes and resources */
	CLEAR_HEAD(phead);
	for (i = 0; i < (int) JOB_ATR_LAST; i++) {
		(void) (job_attr_def + i)->at_encode(get_jattr(pjob, i), &phead, (job_attr_def + i)->at_name, NULL, ATR_ENCODE_MOM, NULL);
	}
	attrl_fixlink(&phead);

	psatl = (svrattrl *) GET_NEXT(phead);
	for (ps = psatl; ps; ps = (svrattrl *) GET_NEXT(ps->al_link)) {
		if (ps->al_resc != NULL) {
			fprintf(fp, "%s.%s[%s]=%s\n", EVENT_JOB_OBJECT,
				ps->al_name, ps->al_resc, ps->al_value);
		} else {
			if (strcmp(ps->al_name, ATTR_v) == 0)
				fprintf(fp, "%s.%s=\"\"\"%s\"\"\"\n", EVENT_JOB_OBJECT, ps->al_name, ps->al_value);
#if MOM_ALPS
			else if (strcmp(ps->al_name, ATTR_tolerate_node_failures) == 0)
				fprintf(fp, "%s.%s=none\n", EVENT_JOB_OBJECT, ps->al_name);
#endif
			else
				fprintf(fp, "%s.%s=%s\n", EVENT_JOB_OBJECT, ps->al_name, ps->al_value);
		}
	}

	free_attrlist(&phead);
}

/**
 * @brief
 *	Alarm handling function to the set_alarm() call.
 *
 */
static void
run_hook_alarm(void)
{
	run_exit = -3;
}

/**
 * @brief
 *	Print to file pointed to by 'fp', the values in a vnl_t structure 'vp'.
 *
 * @param[in] 	fp - pointer to file to dump output into
 * @param[in]	head_str - some string to prefix outputted data
 * @param[in]	vp - pointer to a vnl_t structure containing data to print out.
 *
 * @note
 * 	vnl_t entry with attribute ATTR_NODE_TopologyInfo is ignored.
 *
 * @return none
 *
 */
void
fprint_vnl(FILE *fp, char *head_str, vnl_t *vp)
{
	char *p;
	int i, j;
	char *attname = NULL;
	char *attres = NULL;

	if ((fp == NULL) || (head_str == NULL) || (vp == NULL))
		return;

	for (i = 0; i < vp->vnl_used; i++) {
		vnal_t *vnalp;

		vnalp = VNL_NODENUM(vp, i);

		for (j = 0; j < vnalp->vnal_used; j++) {
			vna_t *vnap;

			vnap = VNAL_NODENUM(vnalp, j);
			attname = vnap->vna_name;
			if (strcmp(attname, ATTR_NODE_TopologyInfo) == 0) {
				/* ignoring this internal attribute  */
				/* since it tends to be quite a big data, */
				/* plus no support right now in hooks */
				/* to modify this data. */
				continue;
			}
			attres = NULL;
			p = strrchr(vnap->vna_name, '.');
			if (p != NULL) {
				*p = '\0';
				p++;
				attres = p;
			}

			if (attres != NULL) {
				fprintf(fp, "%s[\"%s\"].%s[%s]=%s\n",
					head_str,
					vnalp->vnal_id, attname, attres,
					vnap->vna_val);
			} else {
				fprintf(fp, "%s[\"%s\"].%s=%s\n",
					head_str,
					vnalp->vnal_id, attname,
					vnap->vna_val);
			}
			if (p != NULL)
				*p = '.'; /* restore value */
		}
	}
	fflush(fp);
}

/**
 * @brief
 *	Print to file pointed to by 'fp', the values of each job in the 'joblist'.
 *
 * @param[in] 	fp - pointer to file to dump output into
 * @param[in]	head_str - some string to prefix outputted data
 * @param[in]	joblist - pointer to a  list of jobs.
 *
 * @return none
 *
 */
void
fprint_joblist(FILE *fp, char *head_str, pbs_list_head *joblist)
{
	job *pjob;
	int i;
	pbs_list_head phead;
	svrattrl *psatl;
	svrattrl *ps;
	char *jobid;
	int keeping = 0;
	char *std_file = NULL;

	if ((fp == NULL) || (head_str == NULL) || (joblist == NULL))
		return;

	for (pjob = (job *) GET_NEXT(*joblist); pjob;
	     pjob = (job *) GET_NEXT(pjob->ji_alljobs)) {

		jobid = pjob->ji_qs.ji_jobid;
		/* Now print job attributes and resources */
		CLEAR_HEAD(phead);
		for (i = 0; i < (int) JOB_ATR_LAST; i++) {
			(void) (job_attr_def + i)->at_encode(get_jattr(pjob, i), &phead, (job_attr_def + i)->at_name, NULL, ATR_ENCODE_MOM, NULL);
		}
		attrl_fixlink(&phead);

		psatl = (svrattrl *) GET_NEXT(phead);
		for (ps = psatl; ps; ps = (svrattrl *) GET_NEXT(ps->al_link)) {
			if (ps->al_resc != NULL) {
				fprintf(fp, "%s[\"%s\"].%s[%s]=%s\n",
					head_str, jobid, ps->al_name, ps->al_resc,
					ps->al_value);
			} else {
				fprintf(fp, "%s[\"%s\"].%s=%s\n",
					head_str,
					jobid, ps->al_name, ps->al_value);
			}
		}
		if ((pjob->ji_qs.ji_svrflags & JOB_SVFLG_HERE) != 0) {
			fprintf(fp, "%s[\"%s\"]._msmom=True\n",
				head_str, jobid);
		}

		std_file = std_file_name(pjob, StdOut, &keeping);
		fprintf(fp, "%s[\"%s\"]._stdout_file=%s\n",
			head_str, jobid, std_file ? std_file : "");
		std_file = std_file_name(pjob, StdErr, &keeping);
		fprintf(fp, "%s[\"%s\"]._stderr_file=%s\n",
			head_str, jobid, std_file ? std_file : "");
		free_attrlist(&phead);
	}
}

/**
 * @brief
 *	Add to a vnl_t structure 'p_vnlp' the natural vnode information.
 *
 * @Note
 *	This uses the mom_short_name as natural vnode name.
 *
 * @param[in/out] p_vnlp - the vnl_t structure to put natural vnode info.
 *			 - Be sure to free up the *p_vnlp structure when
 *			 - done as it is malloced.
 *
 * @return none
 *
 */
static void
add_natural_vnode_info(vnl_t **p_vnlp)
{
	char bufs[BUFSIZ];
	char *msgbuf;

	if (*p_vnlp == NULL) {
		if (vnl_alloc(p_vnlp) == NULL) {
			log_err(errno, __func__, "Failed to allocate vnlp");
			return;
		}
	}

	snprintf(bufs, sizeof(bufs), "%d", num_pcpus);
	if (vn_addvnr(*p_vnlp, mom_short_name, ATTR_NODE_pcpus, bufs, 0, 0, NULL) == -1) {
		pbs_asprintf(&msgbuf,
			     "Failed to add '%s %s=%s' to vnode list",
			     mom_short_name, ATTR_NODE_pcpus, bufs);
		log_err(-1, __func__, msgbuf);
		free(msgbuf);
		return;
	}

	snprintf(bufs, sizeof(bufs), "%d", num_acpus);
	if (vn_addvnr(*p_vnlp, mom_short_name, "resources_available.ncpus", bufs, 0, 0, NULL) == -1) {
		pbs_asprintf(&msgbuf,
			     "Failed to add '%s %s=%s' to vnode list",
			     mom_short_name, "resources_available.ncpus", bufs);
		log_err(-1, __func__, msgbuf);
		free(msgbuf);
		return;
	}

	snprintf(bufs, sizeof(bufs), "%llukb", av_phy_mem);
	if (vn_addvnr(*p_vnlp, mom_short_name, "resources_available.mem", bufs, 0, 0, NULL) == -1) {
		pbs_asprintf(&msgbuf,
			     "Failed to add '%s %s=%s' to vnode list",
			     mom_short_name, "resources_available.mem", bufs);
		log_err(-1, __func__, msgbuf);
		free(msgbuf);
		return;
	}

	if (vn_addvnr(*p_vnlp, mom_short_name, "resources_available.arch",
		      arch(NULL), 0, 0, NULL) == -1) {
		snprintf(log_buffer, sizeof(log_buffer),
			 "Failed to add '%s %s=%s' to vnode list",
			 mom_host, "arch", arch(NULL));
		log_err(-1, __func__, log_buffer);
		return;
	}

	if (vn_addvnr(*p_vnlp, mom_short_name, "pbs_version",
		      PBS_VERSION, 0, 0, NULL) == -1) {
		snprintf(log_buffer, sizeof(log_buffer),
			 "Failed to add '%s %s=%s' to vnode list",
			 mom_short_name, "pbs_version", PBS_VERSION);
		log_err(-1, __func__, log_buffer);
		return;
	}
}

/**
 * @brief
 *	Free any vnl_t structures contained in a hook_vnl_action list and
 *	delete the individual list structure.
 *
 * @param[in]	listh - the head of the list of hook_vnl_action structures
 *
 * @return none
 *
 */
void
vna_list_free(pbs_list_head listh)
{
	struct hook_vnl_action *pvna;
	struct hook_vnl_action *nxt;

	pvna = (struct hook_vnl_action *) GET_NEXT(listh);

	while (pvna != NULL) {
		nxt = (struct hook_vnl_action *) GET_NEXT(pvna->hva_link);
		if (pvna->hva_vnl)
			vnl_free((vnl_t *) pvna->hva_vnl);
		delete_link(&pvna->hva_link);
		free(pvna);
		pvna = nxt;
	}
}

/**
 * @brief
 *	Copies 'src_file' to 'dest_file' and set 'dest_file' permission
 *	from 'pjob''s data.
 *
 * @param[in]	src_file - the source file to copy
 * @param[in]	dest_file - the destination file
 * @param[in]	pjob - job whose permissions will be used to the
 * 			'dest_file'.
 *
 * @return int
 * @retval 0  - for success
 * @retval -1 - for failure.
 *
 */
static int
copy_file_and_set_owner(char *src_file, char *dest_file, job *pjob)
{
	int st;

	if ((src_file == NULL) || (dest_file == NULL) || (pjob == NULL))
		return -1;

	st = copy_file_internal(src_file, dest_file);

	switch (st) {
		case 0:
			break;
		case COPY_FILE_BAD_INPUT:
			log_errf(errno, __func__,
				 "copy_file_internal: bad input parameter src_file: %s; dest_file: %s",
				 src_file, dest_file);
			return -1;
		case COPY_FILE_BAD_SOURCE:
			snprintf(log_buffer, sizeof(log_buffer),
				 "Failed to open file %s",
				 src_file);
			log_err(errno, __func__, log_buffer);
			return -1;
		case COPY_FILE_BAD_DEST:
			snprintf(log_buffer, sizeof(log_buffer),
				 "Failed to open file copy %s",
				 dest_file);
			log_err(errno, __func__, log_buffer);
			return -1;
		case COPY_FILE_BAD_WRITE:
			snprintf(log_buffer,
				 sizeof(log_buffer),
				 "Failed writing to file %s",
				 dest_file);
			log_err(errno, __func__, log_buffer);
			return -1;
		default:
			snprintf(log_buffer,
				 sizeof(log_buffer),
				 "Unknown copy_file_internal return %d; src_file: %s; dest_file: %s",
				 st, src_file, dest_file);
			log_err(errno, __func__, log_buffer);
			return -1;
	}
#ifndef WIN32
	if (chown(dest_file,
		  pjob->ji_qs.ji_un.ji_momt.ji_exuid,
		  pjob->ji_qs.ji_un.ji_momt.ji_exgid) == -1) {
		snprintf(log_buffer, sizeof(log_buffer),
			 "chown: %s", dest_file);
		log_err(errno, __func__, log_buffer);
		(void) unlink(dest_file);
		return -1;
	}
#else /* Windows */

	if (secure_file2(dest_file,
			 pjob->ji_user->pw_name,
			 READS_MASK | WRITES_MASK | STANDARD_RIGHTS_REQUIRED,
			 "Administrators",
			 READS_MASK | WRITES_MASK | STANDARD_RIGHTS_REQUIRED) == 0) {
		snprintf(log_buffer, LOG_BUF_SIZE, "Unable to change permissions of the file for user: %s, file: %s",
			 pjob->ji_user->pw_name, dest_file);
		log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_JOB, LOG_ERR,
			  __func__, log_buffer);
		(void) unlink(dest_file);
		return -1;
	}
#endif

	return 0;
}

/**
 * @brief
 * 	Add to the vnodes list 'vnl' the data found in the vnode-to-mpi-process
 *	mapping array 'vnode_entry', whose number of entries is in 'num_vnodes'.
 *
 * @param[out]		vnl - pointer to the destination list of vnodes.
 * @param[in]		vnode_entry - array of vnode-to-mpi-process mappings.
 * @param[in]		num_vnodes - numbver of entries in 'vnode_entry'.
 * @param[out]		matched_vnode - set to 1 if one of the added vnodes
 * 					matches the natural vnode.
 * @return int
 * @retval 0  - for success
 * @retval -1 - for failure
 *
 */
static int
vnl_add_vnode_entries(vnl_t *vnl, vmpiprocs *vnode_entry, int num_vnodes,
		      int *matched_nvnode)
{
	int i, rc;
	char bufs[BUFSIZ];
	char *msgbuf;
	char *v_name = NULL;
	int v_cpus = 0;
	Long v_mem = 0;
	char *v_cpus_o_str = NULL;
	char *v_mem_o_str = NULL;

	if ((vnl == NULL) || (vnode_entry == NULL) ||
	    (matched_nvnode == NULL)) {
		return (0); /* incomplete input, no need to proceed  */
	}

	for (i = 0; i < num_vnodes; i++) {

		v_name = vnode_entry[i].vn_vname;

		/* matching vnode names is case-insensitive, */
		/* see find_nodebyname() where it does strcasecmp() */
		/* Also, we need to use the str case of mom_short_name */
		/* which is the natural vnode key returned by */
		/* pbs.get_local_nodename() */
		if (strcasecmp(v_name, mom_short_name) == 0)
			*matched_nvnode = 1;

		v_cpus = vnode_entry[i].vn_cpus;
		if ((v_cpus_o_str = vn_exist(vnl, v_name,
					     RESCASSN_NCPUS)) != NULL) {
			v_cpus += atoi(v_cpus_o_str);
		}

		snprintf(bufs, sizeof(bufs), "%d", v_cpus);
		rc = vn_addvnr(vnl, v_name, RESCASSN_NCPUS, bufs, 0, 0, NULL);
		if (rc == -1) {
			pbs_asprintf(&msgbuf, "%s:failed to add '%s=%s'",
				     v_name, RESCASSN_NCPUS, bufs);
			log_err(-1, __func__, msgbuf);
			free(msgbuf);
			return (-1);
		}

		if ((vnode_entry[i].vn_host != NULL) &&
		    (vnode_entry[i].vn_host->hn_host != NULL)) {

			snprintf(bufs, sizeof(bufs), "%s", vnode_entry[i].vn_host->hn_host);
			rc = vn_addvnr(vnl, v_name, RESCASSN_HOST, bufs, 0, 0, NULL);
			if (rc == -1) {
				pbs_asprintf(&msgbuf, "%s:failed to add '%s=%s'",
					     v_name, "host", bufs);
				log_err(-1, __func__, log_buffer);
				free(msgbuf);
				return (-1);
			}
		}

		v_mem = vnode_entry[i].vn_mem;
		if ((v_mem_o_str = vn_exist(vnl, v_name,
					    RESCASSN_MEM)) != NULL) {
			v_mem += atoL(v_mem_o_str);
		}

		snprintf(bufs, sizeof(bufs), "%lldkb", v_mem);
		rc = vn_addvnr(vnl, v_name, RESCASSN_MEM, bufs, 0, 0, NULL);

		if (rc == -1) {
			pbs_asprintf(&msgbuf, "%s:failed add '%s=%s'",
				     v_name, RESCASSN_MEM, bufs);
			log_err(-1, __func__, msgbuf);
			free(msgbuf);
			return (-1);
		}
	}
	return (0);
}

/**
 * @brief
 *	Duplicates pointer to hooks parameters.
 *
 * @param[in]   php - structure for duplication
 *
 * @return new_php	for success
 * @return NULL		for error
 *
 */
mom_process_hooks_params_t
	*
	duplicate_php(mom_process_hooks_params_t *php)
{
	mom_process_hooks_params_t *new_php;

	if ((new_php = (mom_process_hooks_params_t *) malloc(
		     sizeof(mom_process_hooks_params_t))) == NULL) {
		log_err(errno, __func__, MALLOC_ERR_MSG);
		return NULL;
	}

	new_php->hook_event = php->hook_event;
	new_php->req_user = php->req_user;
	new_php->req_host = php->req_host;
	new_php->hook_msg = php->hook_msg;
	new_php->msg_len = php->msg_len;
	new_php->update_svr = php->update_svr;
	new_php->hook_input = php->hook_input;
	new_php->hook_output = php->hook_output;
	new_php->parent_wait = php->parent_wait;

	return new_php;
}

/**
 * @brief
 *	Runs the hook 'phook' in a child process in response to 'event_type'
 *	with input parameter 'hook_input'.
 *
 * @param[in]	phook	- pointer to hook being run.
 * @param[in]	event_type - the hook event type (e.g. HOOK_EVENT_EXECJOB_BEGIN)
 * @param[in]	hook_input - struct containing input parameters.
 * @param[in]	req_user - user executing the hook
 * @param[in]	req_host - where the hook is executing
 * @param[in]   parent_wait - if set to 1, parent process will wait for hook to
 *		finish; otherwise, it gets run in the background and when done,
 *		the 'post_func' below will execute.
 *		With parent_wait == 0, file_in, file_out are not filled in.
 * @param[in]   post_func - to function to execute when backgrounded hook
 *		finishes execution.
 *		, parent process will wait for job
 * @param[in/out] file_in 	- be filled in by the hook input file used
 * @param[in/out] file_out	- be filled in by the hook output file used
 * @param[in/out] file_data	- be filled in by the hook server data file used
 * @param[in]	  file_size	- max size of file_in and file_out string
 *				  buffers.
 * @param[in]	  php - holds the arguments from mom_process_hooks function
 *
 *
 * @return int		the exit value of the executing hook script
 * @return 0		for success
 * @return != 0		for error
 *
 */
static int
run_hook(hook *phook, unsigned int event_type, mom_hook_input_t *hook_input,
	 char *req_user, char *req_host,
	 int parent_wait, void (*post_func)(struct work_task *),
	 char *file_in, char *file_out, char *file_data, size_t file_size,
	 mom_process_hooks_params_t *php)
{

	FILE *fp = NULL;
	char in_data[LOG_BUF_SIZE + 1];
	char hook_inputfile[MAXPATHLEN + 1];
	char hook_outputfile[MAXPATHLEN + 1];
	char hook_datafile[MAXPATHLEN + 1];
	char script_copy[MAXPATHLEN + 1];
	char hook_config_copy[MAXPATHLEN + 1];
	char rescdef_copy[MAXPATHLEN + 1];
	char log_file[MAXPATHLEN + 1];
	char *script_file = NULL;
	char *rescdef_file = NULL;
	int waitst = 0;
	char pypath[MAXPATHLEN + 1];
	pid_t child;
	struct stat sbuf;
	int runas_jobuser = 0; /* if 1, run as job's euser */
	struct work_task *ptask;
	vnl_t *vnl = NULL;
	vnl_t *vnl_fail = NULL;
	pbs_list_head *failed_mom_list = NULL;
	pbs_list_head *succeeded_mom_list = NULL;
	vnl_t *nv = NULL;
	int vnl_created = 0;
	job *pjob = NULL;
	int matched_nvnode = 0; /* match natural vnode */
	char *arg[14];
	pid_t myseq; /* just some unique sequence number */
	char logmask[BUFSIZ];
	char path_hooks_rescdef[MAXPATHLEN + 1];
	char cmdline[2 * BUFSIZ + 16]; /* Additional bytes for command options */
	struct passwd *pwdp = NULL;
#ifdef WIN32
	FILE *fp2 = NULL;
	pio_handles pio;
#endif
	char *progname = NULL;
	char **argv = NULL;
	char **env = NULL;
	char *env_str = NULL;
	pid_t pid = -1;
	int k;
	char script_path[MAXPATHLEN + 1];
	char hook_config_path[MAXPATHLEN + 1];
	char *p;
	pbs_list_head *jobs_list = NULL;
	char *pc;
	int keeping = 0;
	char *std_file = NULL;
	reliable_job_node *rjn;

	if ((phook == NULL) || (req_user == NULL) || (req_host == NULL)) {
		log_err(-1, __func__, "Bad input received!");
		return -1;
	}

	if (phook->hook_name == NULL) {
		log_err(-1, __func__, "hook has no name");
		return -1;
	}

	if (phook->script == NULL) {
		log_errf(-1, __func__, "hook %s has no script value", phook->hook_name);
		return -1;
	}
	if (hook_input == NULL) {
		log_err(-1, __func__, "missing input argument to event");
		return (-1);
	}

	/* Validate input parameters */

	switch (event_type) {
		case HOOK_EVENT_EXECJOB_LAUNCH:
		case HOOK_EVENT_EXECJOB_ATTACH:
			if (event_type == HOOK_EVENT_EXECJOB_LAUNCH) {
				progname = hook_input->progname;
				argv = hook_input->argv;
				env = hook_input->env;
			} else {
				pid = hook_input->pid;
			}
			/* falls through */
		case HOOK_EVENT_EXECJOB_BEGIN:
		case HOOK_EVENT_EXECJOB_RESIZE:
		case HOOK_EVENT_EXECJOB_PROLOGUE:
		case HOOK_EVENT_EXECJOB_PRETERM:
		case HOOK_EVENT_EXECJOB_EPILOGUE:
		case HOOK_EVENT_EXECJOB_END:
		case HOOK_EVENT_EXECJOB_ABORT:
		case HOOK_EVENT_EXECJOB_POSTSUSPEND:
		case HOOK_EVENT_EXECJOB_PRERESUME:
			pjob = hook_input->pjob;
			if ((event_type == HOOK_EVENT_EXECJOB_LAUNCH) ||
			    (event_type == HOOK_EVENT_EXECJOB_PROLOGUE)) {
				vnl = (vnl_t *) hook_input->vnl;
				vnl_fail = (vnl_t *) hook_input->vnl_fail;
				failed_mom_list = hook_input->failed_mom_list;
				succeeded_mom_list = hook_input->succeeded_mom_list;
			}
			break;
		case HOOK_EVENT_EXECHOST_PERIODIC:
			jobs_list = hook_input->jobs_list;
			/* falls through */
		case HOOK_EVENT_EXECHOST_STARTUP:
			vnl = hook_input->vnl;
			break;
		default:
			log_err(-1, __func__, "unknown hook event");
			return (-1);
	}

	snprintf(pypath, MAXPATHLEN, "%s/bin/pbs_python", pbs_conf.pbs_exec_path);
	run_exit = 0;

	if ((phook->user == HOOK_PBSUSER) && (event_type & USER_MOM_EVENTS))
		runas_jobuser = 1;

	child = fork();
	if (child > 0) { /* parent */

		if (!parent_wait) {
			ptask = set_task(WORK_Deferred_Child, child,
					 post_func, phook);
			if (!ptask) {
				log_err(errno, __func__, msg_err_malloc);
				return (-1);
			}
			if (php) {
				ptask->wt_parm2 = (void *) php;
				if (php->hook_input && php->hook_input->pjob)
					php->hook_input->pjob->ji_bg_hook_task = ptask;
			}
			return (0); /* no hook output file at this time */
		} else if (php)
			php->child = child;

		set_alarm(phook->alarm, run_hook_alarm);
		while (waitpid(child, &waitst, 0) < 0) { /* error on wait */
			if (errno != EINTR) {		 /* continue loop on signal */
				run_exit = -5;
				break;
			}
			kill(-child, SIGKILL);
		}
		set_alarm(0, NULL);
		kill(-child, SIGKILL);
		if (run_exit == 0) {
			if (WIFEXITED(waitst)) {
				run_exit = WEXITSTATUS(waitst);
			} else if (WIFSIGNALED(waitst)) {
				run_exit = -4;
			}
		} else {
			log_eventf(PBSEVENT_DEBUG, PBS_EVENTCLASS_HOOK, LOG_INFO, phook->hook_name,
				   "prematurely completed %s, exit=%d", ((struct python_script *) (phook->script))->path, run_exit);
		}

	} else {
		run_exit = 255;
		if (!child) { /* child */
			/* releasing ports */
			tpp_terminate();
			net_close(-1);
			setsid();

			myseq = getpid();
		} else if (errno == ENOSYS) {
			/* fork not available continue in foreground */
			myseq = rand();
			child = myseq;
			if (php)
				php->child = child;
		} else {
			log_err(errno, __func__, "fork failed");
			goto run_hook_exit;
		}

		snprintf(path_hooks_rescdef, MAXPATHLEN, "%s%s", path_hooks, PBS_RESCDEF);
		pbs_strncpy(hook_config_path, ((struct python_script *) phook->script)->path, sizeof(hook_config_path));
		p = strstr(hook_config_path, HOOK_SCRIPT_SUFFIX);
		if (p != NULL) {
			/* replace <HOOK_SCRIPT_SUFFIX> with <HOOK_CONFIG_SUFFIX>: */
			/* must copy up to HOOK_SCRIPT_SUFFIX length so as to not */
			/* overflow */
			snprintf(p, sizeof(hook_config_path) - (p - hook_config_path), "%s", HOOK_CONFIG_SUFFIX);
			if (stat(hook_config_path, &sbuf) != 0) {
				hook_config_path[0] = '\0';
			}
		} else
			hook_config_path[0] = '\0';

		if (runas_jobuser) {

			/* use world writable spool directory */

			snprintf(hook_inputfile, MAXPATHLEN, FMT_HOOK_INFILE, path_spool, hook_event_as_string(event_type), phook->hook_name, myseq);
			snprintf(hook_outputfile, MAXPATHLEN, FMT_HOOK_OUTFILE, path_spool, hook_event_as_string(event_type), phook->hook_name, myseq);
			snprintf(hook_datafile, MAXPATHLEN, FMT_HOOK_DATAFILE, path_spool, hook_event_as_string(event_type), phook->hook_name, myseq);
			snprintf(script_copy, MAXPATHLEN, FMT_HOOK_SCRIPT, path_spool, myseq);
			snprintf(hook_config_copy, MAXPATHLEN, FMT_HOOK_CONFIG, path_spool, myseq);
			snprintf(rescdef_copy, MAXPATHLEN, FMT_HOOK_RESCDEF, path_spool, myseq);
			snprintf(log_file, MAXPATHLEN, FMT_HOOK_LOG, path_spool, myseq);

			/*
			 * get the password entry for the user under which the
			 * job is to be run we do this now to save a few things
			 * in the job structure
			 * All this info needed to pre-fetch for becomeuser()
			 * to work.
			 */

			if (pjob == NULL) {
				log_err(-1, __func__, "No job parameter passed!");
				goto run_hook_exit;
			}

			pwdp = check_pwd(pjob);
			if (pwdp == NULL) {
				log_event(PBSEVENT_JOB | PBSEVENT_SECURITY, PBS_EVENTCLASS_JOB, LOG_ERR, pjob->ji_qs.ji_jobid, log_buffer);
				goto run_hook_exit;
			}

			if (pjob->ji_grpcache == NULL) {
				log_errf(-1, __func__, "job %s has no ji_grpcache value", pjob->ji_qs.ji_jobid);
				goto run_hook_exit;
			}

			pjob->ji_qs.ji_un.ji_momt.ji_exuid = pjob->ji_grpcache->gc_uid;
			pjob->ji_qs.ji_un.ji_momt.ji_exgid = pjob->ji_grpcache->gc_gid;

			pbs_strncpy(script_path, ((struct python_script *) phook->script)->path, sizeof(script_path));

			/* copy hook_config_path to user-accessible [PBS_HOME]/path_spool. */
			if (hook_config_path[0] != '\0') {
				if (copy_file_and_set_owner(hook_config_path, hook_config_copy, pjob) == -1)
					goto run_hook_exit;
				/* set hook_config_path to hook_config_copy if the copying was successful */
				snprintf(hook_config_path, sizeof(hook_config_path), "%s", hook_config_copy);
			}
			/* copy script_path to user-accessible [PBS_HOME]/path_spool */
			if (copy_file_and_set_owner(script_path, script_copy, pjob) == -1)
				goto run_hook_exit;
			if (stat(path_hooks_rescdef, &sbuf) == 0) {
				if (copy_file_and_set_owner(path_hooks_rescdef, rescdef_copy, pjob) == -1)
					goto run_hook_exit;
				rescdef_file = (char *) rescdef_copy;
			}
			script_file = (char *) script_copy;

#ifndef WIN32
			if (chown(script_file, pjob->ji_qs.ji_un.ji_momt.ji_exuid, pjob->ji_qs.ji_un.ji_momt.ji_exgid) == -1) {
				log_errf(errno, __func__, "chown: %s", script_file);
				goto run_hook_exit;
			}
#else /* Windows */

			if (secure_file2(script_file, pjob->ji_user->pw_name, READS_MASK | WRITES_MASK | STANDARD_RIGHTS_REQUIRED,
					 "Administrators", READS_MASK | WRITES_MASK | STANDARD_RIGHTS_REQUIRED) == 0) {
				log_eventf(PBSEVENT_ERROR, PBS_EVENTCLASS_JOB, LOG_ERR, __func__,
					   "Unable to change permissions of the script file for user: %s, file: %s",
					   pjob->ji_user->pw_name, script_file);
				goto run_hook_exit;
			}
#endif

			if ((fp = fopen(hook_inputfile, "w")) == NULL) {
				log_errf(errno, __func__, "open of input file %s failed!", hook_inputfile);
				goto run_hook_exit;
			}

#ifndef WIN32
			if (chown(hook_inputfile, pjob->ji_qs.ji_un.ji_momt.ji_exuid, pjob->ji_qs.ji_un.ji_momt.ji_exgid) == -1) {
				log_errf(errno, __func__, "chown: %s", hook_inputfile);
				goto run_hook_exit;
			}

			/* NOTE: "launch" hook is already running as the user */
			if (becomeuser(pjob) != 0) {
				char *jobuser;

				jobuser = get_jattr_str(pjob, JOB_ATR_euser);
				log_errf(errno, __func__, "Unable to become user %s!", (jobuser ? jobuser : "<job euser unset>"));
				goto run_hook_exit;
			}
#else
			if (secure_file2(hook_inputfile, pjob->ji_user->pw_name, READS_MASK | WRITES_MASK | STANDARD_RIGHTS_REQUIRED,
					 "Administrators", READS_MASK | WRITES_MASK | STANDARD_RIGHTS_REQUIRED) == 0) {
				log_eventf(PBSEVENT_ERROR, PBS_EVENTCLASS_JOB, LOG_ERR, __func__,
					   "Unable to change permissions of the hook input file for user: %s, file: %s",
					   pjob->ji_user->pw_name, hook_inputfile);
				goto run_hook_exit;
			}

			/* Force create the log file, to secure afterwards */
			if ((fp2 = fopen(log_file, "w")) == NULL) {
				log_errf(errno, __func__, "open of log file %s failed!", log_file);
				goto run_hook_exit;
			}
			fclose(fp2);
			fp2 = NULL;

			if (secure_file2(log_file, pjob->ji_user->pw_name, READS_MASK | WRITES_MASK | STANDARD_RIGHTS_REQUIRED,
					 "Administrators", READS_MASK | WRITES_MASK | STANDARD_RIGHTS_REQUIRED) == 0) {
				log_eventf(PBSEVENT_ERROR, PBS_EVENTCLASS_JOB, LOG_ERR, __func__,
					   "Unable to change permissions of the log file for user: %s, file: %s",
					   pjob->ji_user->pw_name, log_file);
				goto run_hook_exit;
			}
#endif

			/*
			 * chdir to path_spool like a job
			 * NOTE: For launch hooks, it is already in the
			 * environment of the job, so we don't want to
			 * disturb its current working directory.
			 */
			if (chdir(path_spool) != 0)
				log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_HOOK, LOG_WARNING, phook->hook_name, "unable to go to spool directory");
		} else { /* run as root */
			snprintf(hook_inputfile, MAXPATHLEN, FMT_HOOK_INFILE, path_hooks_workdir, hook_event_as_string(event_type), phook->hook_name, myseq);
			snprintf(hook_outputfile, MAXPATHLEN, FMT_HOOK_OUTFILE, path_hooks_workdir, hook_event_as_string(event_type), phook->hook_name, myseq);
			snprintf(hook_datafile, MAXPATHLEN, FMT_HOOK_DATAFILE, path_hooks_workdir, hook_event_as_string(event_type), phook->hook_name, myseq);

			script_file = ((struct python_script *) phook->script)->path;
			if (script_file == NULL) {
				log_event(PBSEVENT_DEBUG, PBS_EVENTCLASS_HOOK, LOG_ERR, phook->hook_name, "No script file found!");
				goto run_hook_exit;
			}

			if (stat(path_hooks_rescdef, &sbuf) == 0)
				rescdef_file = path_hooks_rescdef;

			log_file[0] = '\0';

			if ((fp = fopen(hook_inputfile, "w")) == NULL) {
				log_errf(errno, __func__, "open of input file %s failed!", hook_inputfile);
				goto run_hook_exit;
			}

#ifdef WIN32
			if (secure_file(hook_inputfile, "Administrators", READS_MASK | WRITES_MASK | STANDARD_RIGHTS_REQUIRED) == 0)
				log_eventf(PBSEVENT_ERROR, PBS_EVENTCLASS_JOB, LOG_ERR, __func__,
					   "Failed to change hook input file permissions for file: %s", hook_inputfile);
#endif
			/*
			 * still need to chdir() here. A periodic hook may be
			 * running the hook periodically and may no longer in the
			 * original working directory
			 */
			if (chdir(path_hooks_workdir) != 0)
				log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_HOOK, LOG_WARNING, phook->hook_name, "unable to go to hooks tmp directory");
		}

		switch (event_type) {
			case HOOK_EVENT_EXECJOB_LAUNCH:
			case HOOK_EVENT_EXECJOB_ATTACH:
				if (event_type == HOOK_EVENT_EXECJOB_LAUNCH) {

					if ((progname != NULL) && (progname[0] != '\0'))
						fprintf(fp, "%s.%s=%s\n", EVENT_OBJECT, PY_EVENT_PARAM_PROGNAME, progname);

					if (argv != NULL) {
						k = 0;
						while (argv[k]) {
							fprintf(fp, "%s.%s[%d]=%s\n", EVENT_OBJECT, PY_EVENT_PARAM_ARGLIST, k, argv[k]);
							k++;
						}
					}

					env_str = env_array_to_str(env, ',');
					if (env_str != NULL) {
						if (env_str[0] != '\0')
							fprintf(fp, "%s.%s=\"\"\"%s\"\"\"\n", EVENT_OBJECT, PY_EVENT_PARAM_ENV, env_str);
						free(env_str);
					}
				} else
					fprintf(fp, "%s.%s=%d\n", EVENT_OBJECT, PY_EVENT_PARAM_PID, pid);
				/* fall through */
			case HOOK_EVENT_EXECJOB_BEGIN:
			case HOOK_EVENT_EXECJOB_RESIZE:
			case HOOK_EVENT_EXECJOB_PROLOGUE:
			case HOOK_EVENT_EXECJOB_EPILOGUE:
			case HOOK_EVENT_EXECJOB_END:
			case HOOK_EVENT_EXECJOB_PRETERM:
			case HOOK_EVENT_EXECJOB_ABORT:
			case HOOK_EVENT_EXECJOB_POSTSUSPEND:
			case HOOK_EVENT_EXECJOB_PRERESUME:
				if (pjob == NULL) {
					log_err(-1, __func__, "No job parameter passed!");
					goto run_hook_exit;
				}

				/* pass job parameter */
				fprintf_job_struct(fp, pjob);
				if (pjob->ji_qs.ji_svrflags & JOB_SVFLG_CHKPT)
					fprintf(fp, "%s._checkpointed=True\n", EVENT_JOB_OBJECT);
				if ((pjob->ji_qs.ji_svrflags & JOB_SVFLG_HERE) != 0)
					fprintf(fp, "%s._msmom=True\n", EVENT_JOB_OBJECT);
				std_file = std_file_name(pjob, StdOut, &keeping);
				fprintf(fp, "%s._stdout_file=%s\n", EVENT_JOB_OBJECT, std_file ? std_file : "");
				std_file = std_file_name(pjob, StdErr, &keeping);
				fprintf(fp, "%s._stderr_file=%s\n", EVENT_JOB_OBJECT, std_file ? std_file : "");

				/* pass vnode list parameter */
				if (vnl == NULL) {
					int errcode;

					if (vnl_alloc(&vnl) == NULL) {
						log_err(errno, __func__, "Failed to allocate a vnlp structure");
						goto run_hook_exit;
					}
					vnl_created = 1;

					if (pjob->ji_numnodes == 0) {
						/* this executes in a child process */
						if ((errcode = job_nodes(pjob)) != 0) {
							log_errf(-1, __func__, "job_nodes failed with error %d", errcode);
							goto run_hook_exit;
						}
					}

					/*
					* This looks into pjob->ji_assn_vnodes[] whose
					* entries map exec_vnode, the vnodes
					* assigned to the job along with the
					* allocated cpus and mem for each vnode.
					* For example, given
					*   exec_vnode=(hostA[0]:ncpus=3:mem=100kb)+
					*              (hostB[0]:mem=400kb:ncpus=1)+
					*              (hostA[0]:ncpus=2:mem=200kb)
					* vnl would end up getting accumulated
					* entries:
					*	(hostA[0],ncpus=5,mem=300kb)
					*	(hostB[0],ncpus=1,mem=400kb)
					*/

					if ((vnl_add_vnode_entries(vnl, pjob->ji_assn_vnodes, pjob->ji_num_assn_vnodes, &matched_nvnode) == -1))
						goto run_hook_exit;

					if (matched_nvnode)
						add_natural_vnode_info(&vnl);
				}
				fprint_vnl(fp, EVENT_VNODELIST_OBJECT, vnl);
				if (vnl_fail != NULL)
					fprint_vnl(fp, EVENT_VNODELIST_FAIL_OBJECT, vnl_fail);

				if (failed_mom_list != NULL) {
					rjn = (reliable_job_node *) GET_NEXT(*failed_mom_list);
					while (rjn) {
						fprintf(fp, "%s=%s\n", JOB_FAILED_MOM_LIST_OBJECT, rjn->rjn_host);
						rjn = (reliable_job_node *) GET_NEXT(rjn->rjn_link);
					}
				}

				if (succeeded_mom_list != NULL) {
					rjn = (reliable_job_node *) GET_NEXT(*succeeded_mom_list);
					while (rjn) {
						fprintf(fp, "%s=%s\n", JOB_SUCCEEDED_MOM_LIST_OBJECT, rjn->rjn_host);
						rjn = (reliable_job_node *) GET_NEXT(rjn->rjn_link);
					}
				}

				if (vnl_created) {
					vnl_free(vnl);
					vnl_created = 0;
				}
				break;

			case HOOK_EVENT_EXECHOST_PERIODIC:
				fprintf(fp, "%s.%s=%d\n", EVENT_OBJECT, "freq", phook->freq);
				add_natural_vnode_info(&nv);
				if (nv == NULL)
					fprint_vnl(fp, EVENT_VNODELIST_OBJECT, vnl);
				else {
					if (vnl != NULL)
						vn_merge(nv, vnl, NULL);
					fprint_vnl(fp, EVENT_VNODELIST_OBJECT, nv);
					vnl_free(nv);
				}

				fprint_joblist(fp, EVENT_JOBLIST_OBJECT, jobs_list);

				break;
			case HOOK_EVENT_EXECHOST_STARTUP:
				if (vnl == NULL) {
					/*
					* create a default vnode_list containing
					* the natural vnode, to be used as hook
					* input
					*/
					add_natural_vnode_info(&vnl);
					vnl_created = 1;
				}
				fprint_vnl(fp, EVENT_VNODELIST_OBJECT, vnl);
				if (vnl_created) {
					vnl_free(vnl);
					vnl_created = 0;
					vnl = NULL;
				}
				break;
			default:
				log_errf(-1, __func__, "Unknown event type %d", event_type);
				goto run_hook_exit;
		}

		fprintf(fp, "%s.%s=%s\n", PBS_OBJ, GET_NODE_NAME_FUNC, (char *) mom_short_name);
		fprintf(fp, "%s.%s=%s\n", EVENT_OBJECT, PY_EVENT_TYPE, hook_event_as_string(event_type));
		fprintf(fp, "%s.%s=%s\n", EVENT_OBJECT, PY_EVENT_HOOK_NAME, phook->hook_name);
		fprintf(fp, "%s.%s=%s\n", EVENT_OBJECT, PY_EVENT_HOOK_TYPE, hook_type_as_string(phook->type));
		fprintf(fp, "%s.%s=%s\n", EVENT_OBJECT, "requestor", req_user);
		fprintf(fp, "%s.%s=%s\n", EVENT_OBJECT, "requestor_host", req_host);
		fprintf(fp, "%s.%s=%s\n", EVENT_OBJECT, "user", hook_user_as_string(phook->user));
		fprintf(fp, "%s.%s=%d\n", EVENT_OBJECT, "alarm", phook->alarm);

		if (phook->debug)
			fprintf(fp, "%s.%s=%s\n", EVENT_OBJECT, "debug", hook_datafile);

		fclose(fp);
		fp = NULL;

		arg[0] = (char *) pypath;
		arg[1] = "--hook";
		arg[2] = "-i";
		arg[3] = (char *) hook_inputfile;
		arg[4] = "-o";
		arg[5] = (char *) hook_outputfile;

		if (log_file[0] == '\0') {
			arg[6] = "-L";
			arg[7] = (char *) path_log;
		} else {
			arg[6] = "-l";
			arg[7] = (char *) log_file;
		}
		arg[8] = "-e";
		snprintf(logmask, sizeof(logmask), "%ld", *log_event_mask);
		arg[9] = (char *) logmask;

		if (rescdef_file != NULL) {
			arg[10] = "-r";
			arg[11] = (char *) rescdef_file;
			arg[12] = script_file;
			snprintf(cmdline, sizeof(cmdline),
				 "%s %s %s %s %s %s %s %s %s %s %s %s %s",
				 arg[0], arg[1], arg[2], arg[3], arg[4], arg[5],
				 arg[6], arg[7], arg[8], arg[9], arg[10], arg[11],
				 arg[12]);
			arg[13] = NULL;
		} else {
			arg[10] = script_file;
			snprintf(cmdline, sizeof(cmdline),
				 "%s %s %s %s %s %s %s %s %s %s %s",
				 arg[0], arg[1], arg[2], arg[3], arg[4], arg[5],
				 arg[6], arg[7], arg[8], arg[9], arg[10]);
			arg[11] = NULL;
		}
		log_eventf(PBSEVENT_DEBUG3, PBS_EVENTCLASS_HOOK, LOG_INFO, phook->hook_name,
			   "execve %s runas_jobuser=%d in child pid=%d", cmdline, runas_jobuser, myseq);

		if (hook_config_path[0] == '\0') {
			if (child)
				/* since this is still main mom (not forked), need to unset the hook config environment variable. */
				if (unsetenv(PBS_HOOK_CONFIG_FILE) != 0)
					log_err(-1, __func__, "Failed to unset PBS_HOOK_CONFIG_FILE");
		} else if (setenv(PBS_HOOK_CONFIG_FILE, hook_config_path, 1) != 0) {
			log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_HOOK, LOG_ERR, phook->hook_name, "Failed to set PBS_HOOK_CONFIG_FILE");
			return (-1);
		}

#ifndef WIN32
		/*
		 * We're passing the calling process' (mom's) environment
		 * to python hook execution here, so as to match what's
		 * done on Windows. Also, this allows us to control
		 * pbs_python's execution environment via PBS'
		 * pbs_environment file (better security).
		 * We can provide a  workaround, in case pbs_python does
		 * not execute unless a particular variable is set,
		 * perhaps due to an incorrectly setup host.
		 */
		if (pbs_conf.pbs_conf_file != NULL) {
			if (setenv("PBS_CONF_FILE", pbs_conf.pbs_conf_file, 1) != 0) {
				log_err(errno, __func__, "Failed to set PBS_CONF_FILE");
				goto run_hook_exit;
			}
		}

#ifdef __SANITIZE_ADDRESS__
		/*
		 * Ignore ASAN link order for pbs_python because Python bin
		 * is not compiled with ASAN. There are also some leaks in the Python
		 * library so ignore them by setting exitcode to 0.
		 */
		char *env_asan[] =
		{
				"ASAN_OPTIONS=verify_asan_link_order=0",
				"LSAN_OPTIONS=exitcode=0",
				NULL
		};
		execve(pypath, arg, env_asan);
#else
		execve(pypath, arg, environ);
#endif
	run_hook_exit:
		if (fp != NULL) {
			fclose(fp);
			fp = NULL;
		}
		if (vnl_created)
			vnl_free(vnl);
		log_err(-1, __func__, "execv of hook");
		if (child)
			return run_exit;
		exit(run_exit);
	}
#else
		if (!parent_wait) {
			if (win_popen(cmdline, "w", &pio, NULL) == 0) {
				errno = GetLastError();
				pbs_errno = errno;
				log_eventf(PBSEVENT_DEBUG2, PBS_EVENTCLASS_HOOK, LOG_ERR, __func__, "executing %s failed errno=%d", cmdline, errno);
				win_pclose(&pio);
				return (-1);
			}
			ptask = set_task(WORK_Deferred_Child, (long) pio.pi.hProcess, post_func, phook);
			if (!ptask) {
				log_err(errno, __func__, msg_err_malloc);
				win_pclose(&pio);
				return (-1);
			}
			ptask->wt_aux2 = myseq;
			addpid(pio.pi.hProcess);
			win_pclose2(&pio); /* closes all handles except the process handle */
			ptask->wt_parm2 = (void *) php;
			return (0); /* no hook output file at this time */
		} else if (runas_jobuser) {
			if (pwdp == NULL) {
				log_err(-1, __func__, "runas_jobuser does not have credential set!");
				run_exit = 255;
				goto run_hook_exit;
			}
			(void) win_alarm(phook->alarm, run_hook_alarm);
			char *env_string = NULL;
			struct var_table hook_env;
			hook_env.v_envp = NULL;
			char *pbs_hook_conf = NULL;

			if ((pjob->ji_env.v_envp != NULL) && (phook->user == HOOK_PBSUSER)) {
				/* Duplicate only when the hook user is pbsuser */
				hook_env.v_envp = dup_string_arr(pjob->ji_env.v_envp);
				if (hook_env.v_envp == NULL) {
					log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_JOB, LOG_ERR,
						  __func__, "Unable to set hook environment");
					goto run_hook_exit;
				}
				hook_env.v_ensize = pjob->ji_env.v_ensize;
				hook_env.v_used = pjob->ji_env.v_used;
				if (pbs_hook_conf = getenv("PBS_HOOK_CONFIG_FILE"))
					bld_env_variables(&hook_env, "PBS_HOOK_CONFIG_FILE", pbs_hook_conf);
				env_string = make_envp(hook_env.v_envp);
			}
			wloaduserprofile(pwdp);
			run_exit = wsystem(cmdline, pwdp->pw_userlogin, env_string);
			wunloaduserprofile(pwdp);
			free(env_string);
			(void) win_alarm(0, NULL);
			free_string_array(hook_env.v_envp);
		} else {
			/* The following blocks until after */
			(void) win_alarm(phook->alarm, run_hook_alarm);
			if (win_popen(cmdline, "r", &pio, NULL) == 0) {
				errno = GetLastError();
				pbs_errno = errno;
				log_eventf(PBSEVENT_DEBUG2, PBS_EVENTCLASS_HOOK, LOG_ERR, __func__, "executing %s failed errno=%d", cmdline, errno);

			} else if (GetExitCodeProcess(pio.pi.hProcess, &run_exit) == 0 || run_exit == STILL_ACTIVE) {
				log_err(-1, __func__, "GetExitCodeProcess failed");
				run_exit = 255;
			}
			win_pclose(&pio);
			(void) win_alarm(0, NULL);
		}
		if (php)
			php->child = child;
	run_hook_exit:
		if (fp != NULL)
			fclose(fp);

		if (vnl_created)
			vnl_free(vnl);
	}
#endif

	if (run_exit != 0)
		log_errf(-1, __func__, "execv of %s resulted in nonzero exit status=%d", pypath, run_exit);

	if (file_in != NULL)
		snprintf(file_in, file_size, FMT_HOOK_INFILE, path_hooks_workdir, hook_event_as_string(event_type), phook->hook_name, child);
	if (file_out != NULL)
		snprintf(file_out, file_size, FMT_HOOK_OUTFILE, path_hooks_workdir, hook_event_as_string(event_type), phook->hook_name, child);

	if (file_data != NULL)
		snprintf(file_data, file_size, FMT_HOOK_DATAFILE, path_hooks_workdir, hook_event_as_string(event_type), phook->hook_name, child);

	if (runas_jobuser) {

		/* move [PATH_SPOOL]/<hook input file> to <file_in> where <file_in> is in [PBS_HOME]/mom_priv/hooks/tmp. */
		snprintf(hook_inputfile, MAXPATHLEN, FMT_HOOK_INFILE, path_spool, hook_event_as_string(event_type), phook->hook_name, child);
		if (stat(hook_inputfile, &sbuf) == 0 && file_in != NULL)
			(void) rename(hook_inputfile, file_in);

		/* move [PATH_SPOOL]/<hook output file> to <file_out> where <file_out> is in [PBS_HOME]/mom_priv/hooks/tmp. */
		snprintf(hook_outputfile, MAXPATHLEN, FMT_HOOK_OUTFILE, path_spool, hook_event_as_string(event_type), phook->hook_name, child);
		if (stat(hook_outputfile, &sbuf) == 0 && file_out != NULL)
			(void) rename(hook_outputfile, file_out);

		/* move [PATH_SPOOL]/<hook data file> to <file_data> where <file_data> is in [PBS_HOME]/mom_priv/hooks/tmp. */
		snprintf(hook_datafile, MAXPATHLEN, FMT_HOOK_DATAFILE, path_spool, hook_event_as_string(event_type), phook->hook_name, child);
		if (stat(hook_datafile, &sbuf) == 0 && file_data != NULL)
			(void) rename(hook_datafile, file_data);
		pbs_strncpy(script_path, ((struct python_script *) phook->script)->path, sizeof(script_path));
		pc = strrchr(script_path, '/');
		if (pc == NULL)
			pc = (char *) script_path;
		else
			pc++;

		/* delete [PATH_SPOOL]/<hook script file copy> */
		snprintf(script_copy, MAXPATHLEN, FMT_HOOK_SCRIPT, path_spool, child);

		if (stat(script_copy, &sbuf) == 0)
			(void) unlink(script_copy);

		/* delete [PATH_SPOOL]/<hook config file copy> */
		snprintf(hook_config_copy, MAXPATHLEN, FMT_HOOK_CONFIG, path_spool, child);

		if (stat(hook_config_copy, &sbuf) == 0)
			(void) unlink(hook_config_copy);

		/* delete [PATH_SPOOL]/<resourcedef copy> */
		snprintf(rescdef_copy, MAXPATHLEN, FMT_HOOK_RESCDEF, path_spool, child);

		if (stat(rescdef_copy, &sbuf) == 0)
			(void) unlink(rescdef_copy);

		snprintf(log_file, MAXPATHLEN, FMT_HOOK_LOG, path_spool, child);

		/* Log file generated in [PATH_SPOOL] should be appended to main mom_logs. */
		if ((fp = fopen(log_file, "r")) != NULL) {
			size_t ll;
			char *p;
			char *jobid = NULL;
			int semicolons;

			jobid = pjob->ji_qs.ji_jobid;

			while (fgets(in_data, sizeof(in_data), fp) != NULL) {
				ll = strlen(in_data);
				if (in_data[ll - 1] == '\n')
					/* remove newline */
					in_data[ll - 1] = '\0';

				/* Format of pbs logfile is as follows: */
				/* <time>;<event>;<prog>;<class>;<obj>;<msg> */
				/* There are 5 semicolons before <msg>, */
				/* which is the one we need to get. */
				p = in_data;
				semicolons = 0;
				while (*p != '\0') {
					if (*p == ';') {
						semicolons++;
						if (semicolons == 5)
							break;
					}
					p++;
				}

				if (*p != '\0') {
					*p = '\0';
					p++;
					if (*p != '\0') {

						if (strstr(in_data, ";Job;") != NULL)
							log_event(PBSEVENT_JOB, PBS_EVENTCLASS_JOB, LOG_DEBUG, jobid ? jobid : "", p);
						else
							log_event(PBSEVENT_ADMIN | PBSEVENT_SYSTEM, PBS_EVENTCLASS_HOOK, LOG_DEBUG, "pbs_python", p);
					}
				}
			}
			fclose(fp);
			if (unlink(log_file) == -1)
				log_err(errno, __func__, log_file);
		}
	}

#ifdef WIN32
	if (secure_file(file_out, "Administrators", READS_MASK | WRITES_MASK | STANDARD_RIGHTS_REQUIRED) == 0)
		log_eventf(PBSEVENT_ERROR, PBS_EVENTCLASS_JOB, LOG_ERR, __func__, "Failed to change hook input file permissions for file: %s", file_out);
#endif

	return (run_exit);
}

/**
 * @brief
 *	Free the malloced "path" elemen of py_script.
 *
 * @param[in] py_script - the python_script structure containing the path element.
 *
 * @return Void
 *
 */
void
python_script_free(struct python_script *py_script)
{

	if (py_script != NULL) {
		if (py_script->path != NULL) {
			free(py_script->path);
		}
	} else
		log_err(PBSE_HOOKERROR, __func__, "Python Script is NULL");
}

/**
 * @brief
 *	Creates a path entry to *py_script using 'script_path' as value.
 *
 * @param[in]	script_path - the value to use
 * @param[in] 	py_script - the python_script being instantiated.
 *
 * @Note
 *	If *py_script is not NULL (previously allocated), then
 *	free up previously malloced entries, and reset *py_script to NULL.
 *
 * @return int
 * @retval	0	- for success
 * @retval	-1	- for failure
 *
 */

int
python_script_alloc(const char *script_path, struct python_script **py_script)
{

	struct python_script *tmp_py_script = NULL;
	size_t nbytes = sizeof(struct python_script);
	struct stat sbuf;

	if ((script_path == NULL) || (py_script == NULL)) {
		log_err(-1, __func__, "Bad input parameters");
		return (-1);
	}

	if ((stat(script_path, &sbuf) == -1)) {
		return (0); /* could not stat script, so nothing to set */
	}

	if (*py_script != NULL) {
		if ((*py_script)->path != NULL) {
			free((*py_script)->path);
		}
		free(*py_script);
	}

	*py_script = NULL; /* init, to be safe */

	if (!(tmp_py_script = (struct python_script *) malloc(nbytes))) {
		log_err(errno, __func__, "failed to malloc struct python_script");
		goto python_script_alloc_exit;
	}
	(void) memset(tmp_py_script, 0, nbytes);

	tmp_py_script->path = strdup(script_path);
	if (tmp_py_script->path == NULL) {
		log_err(errno, __func__, "failed to malloc path");
		goto python_script_alloc_exit;
	}

	/* ok, we are set with py_script */
	*py_script = tmp_py_script;
	return 0;

python_script_alloc_exit:
	if (tmp_py_script != NULL) {
		if (tmp_py_script->path != NULL) {
			free(tmp_py_script->path);
		}
		free(tmp_py_script);
		tmp_py_script = NULL;
	}
	return -1;
}

/**
 * @brief
 *	Runs a periodic hook in the background.
 *
 * @param[in] 	phook - hook to run.
 *
 * @return none
 *
 */
void
run_periodic_hook_bg(hook *phook)
{
	int rc;
	mom_hook_input_t hook_input;

	if (phook == NULL) {
		log_err(-1, __func__, "bad input parameter");
		return;
	}

	if (phook->enabled == FALSE) {
		log_event(PBSEVENT_DEBUG3, PBS_EVENTCLASS_HOOK,
			  LOG_ERR, phook->hook_name,
			  "periodic hook has been disabled. Skipping hook");
		return;
	}

	if (phook->script == NULL) {
		log_event(PBSEVENT_DEBUG3, PBS_EVENTCLASS_HOOK,
			  LOG_ERR, phook->hook_name,
			  "Hook has no script content. Skipping hook.");
		return;
	}

	/* Run the hook in a child process, and when done, */
	/* execute post_periodic_hook() */
	/* A copy of the system 'vnlp' is passed here */
	/* hook will execute in a child process, and even though */
	/* it may make changes to 'vnlp', it's only in the child */
	/* that will have this copy */

	/* this is the list of vnodes seen by a periodic hook including */
	/* those added by the exechost_startup hook */
	mom_hook_input_init(&hook_input);
	hook_input.vnl = (vnl_t *) vnlp;
	hook_input.jobs_list = &svr_alljobs;

	rc = run_hook(phook, HOOK_EVENT_EXECHOST_PERIODIC, &hook_input,
		      PBS_MOM_SERVICE_NAME, mom_host, 0,
		      post_periodic_hook,
		      NULL, NULL, NULL, 0, NULL);
	if (rc != 0) {
		log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_HOOK,
			  LOG_ERR, phook->hook_name,
			  "Failed to deploy periodic hook");
	}
}

/**
 * @brief
 *	Runs the hook hanging off a work task.
 *
 * @param[in] 	work_task -  task to process.
 *
 * @return none
 *
 */
static void
run_periodic_hook_bg_task(struct work_task *ptask)
{
	hook *phook = (hook *) ptask->wt_parm1;

	if (phook == NULL) {
		log_err(-1, __func__, "A hook has disappeared.");
		return; /* no hook to execute */
	}

	run_periodic_hook_bg(phook);
}

/**
 * @brief
 *	Get the results from 'input_file' of a previously run hook.
 *
 * @param[in] 		input_file -  file to process.
 * @param[in/out] 	accept_flag -  return 1 if event accept flag is true.
 * @param[in/out] 	reject_flag -  return 1 if event reject flag is true.
 * @param[in/out] 	reject_msg -  the reject message if reject_flag is 1.
 * @param[in]		reject_msg_size -  size of reject_msg buffer.
 * @param[out] 		reject_rerunjob -  return 1 if job is to be rerun.
 * @param[out] 		reject_deletejob -  return 1 if job is to be deleted.
 * @param[in/out] 	reboot_flag -  return 1 if pbs should reboot host
 * @param[in/out] 	reboot_cmd -  the command to use by pbs to reboot host.
 * @param[in]		reboot_cmd_size -  size of reboot_cmd buffer.
 * @param[in/out] 	pvnalist -  a linked list of any resultant vnodes list
 * @param[in/out] 	pjob -  job in question, where if present (not NULL),
 *			        it gets filled in with the
 *				"pbs.event().job" entries in 'input_file'.
 *				'pjob' can be NULL in periodic hooks, since
 *				 periodic hooks are not tied to jobs.
 *				 Note that pbs.event().job_list[<jobid>] entries
 *				 in 'input_file' fill in the individual
 *				 <jobid>'s job struct entry in the system, and
 *				 not the passed 'pjob' structure.
 * @param[in]		phook -  hook that executed of which we're getting the
 *				results. If non-NULL, then phook->user is
 *				used to validate 'pbs.event().job.euser' line
 *				in 'input_file'.
 *				If main Mom is reading a job related hook
 *				results file, phook will be null; an entry in
 *				the file should give us the hook name from which
 *				phook is found.
 * @param[in]		copy_file - copy the results file to one who name has
 *				the job id appended.  This is done when in a
 *				child process of Mom.
 * @param[out]		hook_output - struct of parameters to fill in output.
 *
 * @note
 *	If "copy_file" is true, then the processed 'input_file' lines are
 *	saved under the file name:
 *		hook_output<pjob's job-id>
 *	under the same directory where original 'input_file' is located.
 *	One and only one hook's output is being processed in this case.
 *	"Processed" here means the input line has passed some sanity checks, and
 *	deemed to be a valid input line.
 *	This new hook job output file can later be consulted by
 *	send_obit() and record_finish_exec() to retrieve job and vnl updates
 *	made by a hook in a child process.  It is in this case that a line
 *	containing the hook name will appear.  Multiple hooks of the same event
 *	type may be concatenated.
 *
 * @return int
 * @retval	0 for success
 * @retval	non-zero for failure;  the returned parameters (accept_flag,
 *		reject_flag, reject_rerunjob, reject_deletejob, reboot_flag,
 *		reboot_cmd, pvnalist and pjob) may be invalid and should be
 *		ignored.   The list pvnalisst could have mallocate space and
 *		should be freed by the calling program.
 *
 */
int
get_hook_results(char *input_file, int *accept_flag, int *reject_flag,
		 char *reject_msg, int reject_msg_size, int *reject_rerunjob,
		 int *reject_deletejob, int *reboot_flag, char *reboot_cmd,
		 int reboot_cmd_size, pbs_list_head *pvnalist, job *pjob,
		 hook *phook, int copy_file, mom_hook_output_t *hook_output)
{

	char *name_str;
	char *resc_str;
	char *obj_name;
	char *data_value;
	char *vname_str;
	char *jobid_str;
	int rc = -1;
	char *pc, *pc1, *pc2, *pc3, *pc4, *pc5;
	char *in_data = NULL;
	size_t ll;
	FILE *fp;
	char *p;
	int vn_obj_len = strlen(EVENT_VNODELIST_OBJECT);
	int vn_fail_obj_len = strlen(EVENT_VNODELIST_FAIL_OBJECT);
	int job_obj_len = strlen(EVENT_JOBLIST_OBJECT);
	int index;
	int errcode;
	vnl_t *hvnlp = NULL;
	vnl_t *hvnlp_fail = NULL;
	char hook_job_outfile[MAXPATHLEN + 1];
	FILE *fp2 = NULL;
	char *line_data = NULL;
	int line_data_sz;
	long int endpos;
	int start_new_vnl = 1;
	int start_new_vnl_fail = 1;
	struct hook_vnl_action *pvna;
	char hook_euser[PBS_MAXUSER + 1] = {'\0'};
	char *value_type = NULL;
	job *pjob2 = NULL;
	job *pjob2_prev = NULL;
	int found_rerunjob_action = 0;
	int found_deletejob_action = 0;
	int found_joblist = 0;
	int arg_list_entries = 0;
	int b_triple_quotes = 0;
	int e_triple_quotes = 0;
	char buf_data[STRBUF];

	/* Preset hook_euser for later.  If we are reading a job related     */
	/* copy of hook results, there will be one or more (one per hook)    */
	/* pbs_event().hook_euser=<value> entries.  In that case, hook_euser */
	/* is reset to the <value>.  A null string <value> means PBSADMIN.   */
	if (phook && pjob && (phook->user == HOOK_PBSUSER))
		pbs_strncpy(hook_euser, get_jattr_str(pjob, JOB_ATR_euser), sizeof(hook_euser));

	if ((input_file != NULL) && (*input_file != '\0')) {
		fp = fopen(input_file, "r");

		if (fp == NULL) {
			snprintf(log_buffer, sizeof(log_buffer),
				 "failed to open input file %s", input_file);
			log_err(errno, __func__, log_buffer);
			return (1);
		}
	} else {
		log_err(PBSE_INTERNAL, __func__, "bad input_file parameter");
		return (1);
	}

	/* if called by a child of Mom, then process the input file and    */
	/* copy it to a new file based on the job id.  Thus the main Mom   */
	/* process can read it and send any updates required to the Server */
	if (copy_file) {
		/* 'pjob' is used to name the file, and phook->user is used */
		/* to validate 'pbs.event().job.euser" line in  */
		/* 'input_file'. */
		char *p;
		char chr_save = '\0';
		char *p_dir = NULL;

		if ((pjob == NULL) || (phook == NULL)) {
			log_err(PBSE_INTERNAL, __func__, "bad copy_file parameter");
			return (1);
		}

		p = strrchr(input_file, '/');
		if (p != NULL) {
			p++;
			chr_save = *p;
			*p = '\0';
			p_dir = (char *) input_file;
		}

		snprintf(hook_job_outfile, MAXPATHLEN,
			 FMT_HOOK_JOB_OUTFILE, p_dir ? p_dir : "",
			 pjob->ji_qs.ji_jobid);

		if (chr_save != '\0')
			*p = chr_save; /* restore */

		fp2 = fopen(hook_job_outfile, "a");
		if (fp2 == NULL) {
			snprintf(log_buffer, sizeof(log_buffer),
				 "failed to open hook_job_outfile=%s",
				 hook_job_outfile);
			log_err(errno, __func__, log_buffer);
			fclose(fp);
			(void) unlink(input_file);
			return (1);
		}
		/* first record for each hook is one holding the euser  */
		/* as which the hook was executed.  This also indicates */
		/* the start of a new hook stanza in the file.          */
		fprintf(fp2, "%s=%s\n", EVENT_HOOK_EUSER, hook_euser);
	}

	line_data_sz = STRBUF;
	line_data = (char *) malloc((size_t) line_data_sz);
	if (line_data == NULL) {
		log_err(errno, __func__, "malloc failed");
		rc = 1;
		goto get_hook_results_end;
	}
	line_data[0] = '\0';

	if (fseek(fp, 0, SEEK_END) != 0) {
		log_err(errno, __func__, "fseek to end failed");
		rc = 1;
		goto get_hook_results_end;
	}

	endpos = ftell(fp);
	if (fseek(fp, 0, SEEK_SET) != 0) {
		log_err(errno, __func__, "fseek to beginning failed");
		rc = 1;
		goto get_hook_results_end;
	}

	pjob2 = pjob;
	while (fgets(buf_data, STRBUF, fp) != NULL) {
		b_triple_quotes = 0;
		e_triple_quotes = 0;

		if (pbs_strcat(&line_data, &line_data_sz, buf_data) == NULL) {
			goto get_hook_results_end;
		}
		if (in_data != NULL) {
			free(in_data);
		}
		in_data = strdup(line_data); /* preserve line_data */
		if (in_data == NULL) {
			log_err(errno, __func__, "strdup failed");
			rc = 1;
			goto get_hook_results_end;
		}

		if ((p = strchr(in_data, '=')) != NULL) {
			b_triple_quotes = starts_with_triple_quotes(p + 1);
		}

		ll = strlen(in_data);
		if (in_data[ll - 1] == '\n') {
			e_triple_quotes = ends_with_triple_quotes(in_data, 0);

			if (b_triple_quotes && !e_triple_quotes) {
				int jj;

				while (fgets(buf_data, STRBUF, fp) != NULL) {
					if (pbs_strcat(&line_data, &line_data_sz,
						       buf_data) == NULL) {
						goto get_hook_results_end;
					}

					jj = strlen(line_data);
					if ((line_data[jj - 1] != '\n') &&
					    (ftell(fp) != endpos)) {
						/* get more input for
						 * current item.
						 */
						continue;
					}
					e_triple_quotes =
						ends_with_triple_quotes(line_data, 0);

					if (e_triple_quotes) {
						break;
					}
				}

				if ((!b_triple_quotes && e_triple_quotes) ||
				    (b_triple_quotes && !e_triple_quotes)) {
					snprintf(log_buffer, sizeof(log_buffer),
						 "unmatched triple quotes! Skipping  line %s",
						 in_data);
					log_err(PBSE_INTERNAL, __func__, log_buffer);
					/* process a new line */
					line_data[0] = '\0';
					continue;
				}
				free(in_data);
				in_data = strdup(line_data); /* preserve line_data */
				if (in_data == NULL) {
					log_err(errno, __func__, "strdup failed");
					rc = 1;
					goto get_hook_results_end;
				}
				/* remove newline */
				in_data[strlen(in_data) - 1] = '\0';
			} else {
				/* remove newline */
				in_data[ll - 1] = '\0';
			}

		} else if (ftell(fp) != endpos) { /* continued on next line */
			/* get more input for current item.  */
			continue;
		}

		data_value = NULL;
		if ((p = strchr(in_data, '=')) != NULL) {
			*p = '\0';
			p++;
			while (isspace(*p))
				p++;

			if (b_triple_quotes) {
				/* strip triple quotes */
				p += 3;
			}
			data_value = p;
			if (e_triple_quotes) {
				ends_with_triple_quotes(p, 1);
			}
		}

		obj_name = in_data;

		pc = strrchr(in_data, '.');
		if (pc) {
			*pc = '\0';
			pc++;
		} else {
			pc = in_data;
		}
		name_str = pc;

		pc1 = strchr(pc, '[');
		pc2 = strchr(pc, ']');
		resc_str = NULL;
		if (pc1 && pc2 && (pc2 > pc1)) {
			*pc1 = '\0';
			pc1++;
			*pc2 = '\0';
			pc2++;

			/* now let's if there's anything quoted inside */
			pc3 = strchr(pc1, '"');
			if (pc3 != NULL)
				pc4 = strchr(pc3 + 1, '"');
			else
				pc4 = NULL;

			if (pc3 && pc4 && (pc4 > pc3)) {
				pc3++;
				*pc4 = '\0';
				resc_str = pc3;
			} else {
				resc_str = pc1;
			}
		}

		/* at this point, we have */
		/* Given:  pbs.event().<attribute>=<value> */
		/* Given:  pbs.event().job.<attribute>=<value> */
		/* Given:  pbs.event().job.<attribute>[<resc>]=<value> */
		/* Given:  pbs.event().vnode_list[<vname>].<attribute>=<value> */
		/* Given:  pbs.event().vnode_list[<vname>].<attribute>[<resc>]=<value> */
		/* We get: */

		/* obj_name = pbs.event() or "pbs.event().job" or "pbs.event().vnode_list[<vname>]" */
		/* name_str = <attribute> */
		/* resc_str = <resc> */
		/* data_value = <value> */

		if (data_value == NULL) {

			snprintf(log_buffer, sizeof(log_buffer),
				 "%s: no value given", in_data);
			log_err(errno, __func__, log_buffer);
			rc = 1;
			goto get_hook_results_end;
		}

		if (strcmp(obj_name, EVENT_OBJECT) == 0) {
			if (strcmp(name_str, "hook_euser") == 0) {
				pbs_strncpy(hook_euser, data_value, sizeof(hook_euser));
				start_new_vnl = 1;
				/* Need to also clear 'hvnlp' as previous  */
				/* one would have already been saved in */
				/* svr_hook_vnl_actions structure via */
				/* hook_requests_to_server(). Otherwise, */
				/* it would be bad if the same 'hvnlp' */
				/* gets duplicated in svr_hook_vnl_actions */
				/* which could cause a mom crash. */
				hvnlp = NULL;
				hvnlp_fail = NULL;
			} else if ((accept_flag != NULL) &&
				   strcmp(name_str, "accept") == 0) {
				if (strcmp(data_value, "True") == 0)
					*accept_flag = 1;
				else
					*accept_flag = 0;
			} else if ((reject_flag != NULL) &&
				   strcmp(name_str, "reject") == 0) {

				if (strcmp(data_value, "True") == 0)
					*reject_flag = 1;
				else
					*reject_flag = 0;
			} else if ((reject_msg != NULL) &&
				   (strcmp(name_str, "reject_msg") == 0)) {
				pbs_strncpy(reject_msg, data_value,
					    reject_msg_size);
			} else if (strcmp(name_str, PY_EVENT_PARAM_PROGNAME) == 0) {
				char **prog;
				if (hook_output != NULL) {
					/* need to free up here previous value */
					/* in case of multiple hooks! */
					prog = hook_output->progname;
					if (*prog != NULL) {
						free(*prog);
					}
					*prog = strdup(data_value);
				}
			} else if (strcmp(name_str, PY_EVENT_PARAM_ARGLIST) == 0) {
				pbs_list_head *ar_list;

				arg_list_entries++;
				if (hook_output != NULL) {
					ar_list = hook_output->argv;
					/* free previous values at start of new list */
					if (arg_list_entries == 1) {
						free_attrlist(ar_list);
					}
					add_to_svrattrl_list(ar_list, name_str, resc_str,
							     data_value, 0, NULL);
				}
			} else if (strcmp(name_str, PY_EVENT_PARAM_ENV) == 0) {
				if (hook_output != NULL) {
					if (hook_output->env != NULL) {
						free_str_array(hook_output->env);
					}
					hook_output->env = str_to_str_array(data_value, ',');
				}
			}
		} else if ((strcmp(obj_name, EVENT_JOB_OBJECT) == 0) ||
			   (strncmp(obj_name, EVENT_JOBLIST_OBJECT, job_obj_len)) == 0) {

			if (strncmp(obj_name, EVENT_JOBLIST_OBJECT, job_obj_len) == 0) {
				/* NOTE: obj_name here is: pbs.event().job_list[<jobid>] */

				/* important here to look for the leftmost '[' (using strchr)
				 * and the rightmost ']' (using strrchr)
				 * as we can have:
				 *	pbs.event().job_list["23.fest"].<attr>=<val>
				 * 	and "23.fest" is a valid job id.
				 */

				found_joblist = 1;
				if (((pc1 = strchr(obj_name, '[')) != NULL) &&
				    ((pc2 = strrchr(obj_name, ']')) != NULL) &&
				    (pc2 > pc1)) {
					pc1++;	     /*  pc1=<jobid>] */
					*pc2 = '\0'; /* pc1=<jobid>  */
					pc2++;

					/* now let's if there's anything quoted inside */
					pc3 = strchr(pc1, '"');
					if (pc3 != NULL)
						pc4 = strchr(pc3 + 1, '"');
					else
						pc4 = NULL;

					if (pc3 && pc4 && (pc4 > pc3)) {
						pc3++;
						*pc4 = '\0';
						jobid_str = pc3;
					} else {
						jobid_str = pc1;
					}
				} else {
					snprintf(log_buffer, sizeof(log_buffer),
						 "object '%s' does not have a job id!",
						 obj_name);
					log_err(-1, __func__, log_buffer);
					/* process a new line */
					in_data[0] = '\0';
					continue;
				}

				/* jobs list are stored sorted. If current */
				/* jobid does not match previous jobid, */
				/* this means we've now switched to the */
				/* new job's data. */
				if ((pjob2_prev != NULL) &&
				    (strcmp(pjob2_prev->ji_qs.ji_jobid,
					    jobid_str) == 0)) {
					pjob2 = pjob2_prev; /* optimize */
				} else {
					/* working on a new list of job data */
					if (pjob2_prev != NULL) {
						if (*reject_deletejob) {
							/* deletejob takes precedence */
							new_job_action_req(pjob2, phook ? phook->user : HOOK_PBSADMIN, JOB_ACT_REQ_DELETE);
						} else if (*reject_rerunjob) {
							new_job_action_req(pjob2, phook ? phook->user : HOOK_PBSADMIN, JOB_ACT_REQ_REQUEUE);
						}
						/* already sent the action */
						found_rerunjob_action = 0;
						found_deletejob_action = 0;
					}
					pjob2 = find_job(jobid_str);
					pjob2_prev = pjob2;
					if (reject_rerunjob != NULL)
						*reject_rerunjob = 0;
					if (reject_deletejob != NULL)
						*reject_deletejob = 0;
					found_rerunjob_action = 0;
					found_deletejob_action = 0;
				}
			} else {
				/* not a EVENT_JOBLIST object. Switch */
				/* back to passed job object */
				pjob2 = pjob;
			}
			/* Found: <resource_name>,<job_value_type> */
			if (resc_str != NULL) {
				if ((pc5 = strchr(resc_str, ',')) != NULL) {
					*pc5 = '\0';
					pc5++;
					value_type = pc5;
				}
			}

			if (strcmp(name_str, "_rerun") == 0) {
				found_rerunjob_action = 1;
				if (reject_rerunjob == NULL) {
					/* process a new line */
					line_data[0] = '\0';
					continue;
				}
				if (strcmp(data_value, "True") == 0)
					*reject_rerunjob = 1;
				else
					*reject_rerunjob = 0;
			} else if (strcmp(name_str, "_delete") == 0) {
				found_deletejob_action = 1;
				if (reject_deletejob == NULL) {
					/* process a new line */
					line_data[0] = '\0';
					continue;
				}
				if (strcmp(data_value, "True") == 0)
					*reject_deletejob = 1;
				else
					*reject_deletejob = 0;
			} else if (pjob2 != NULL) {
				/* Is attribute not writeable by manager or */
				/* by a server? */
				/* Exempt attributes set by the hook script */
				resc_access_perm = ATR_DFLAG_USWR |
						   ATR_DFLAG_OPWR |
						   ATR_DFLAG_MGWR |
						   ATR_DFLAG_SvWR |
						   ATR_DFLAG_Creat;

				/* identify the attribute by name */
				index = find_attr(job_attr_idx, job_attr_def, name_str);
				if (index < 0) { /* didn`t recognize the name */
					snprintf(log_buffer, sizeof(log_buffer),
						 "object '%s' unrecognized attrbute name %s!",
						 obj_name, name_str);
					log_err(-1, __func__, log_buffer);
					/* process new line */
					line_data[0] = '\0';
					continue;
				}
				if ((index == JOB_ATR_runcount) && ((is_jattr_set(pjob2, JOB_ATR_run_version)) == 0)) {
					snprintf(log_buffer, sizeof(log_buffer),
						 "object '%s': ignoring setting attribute %s,"
						 " talking to a server that does not allow %s modification, ",
						 obj_name, name_str, name_str);
					log_err(-1, __func__, log_buffer);
					/* process new line */
					line_data[0] = '\0';
					continue;
				}

				/* security guard: hook that runs as    */
				/* PBSUSER should not be allowed  */
				/* to modify the euser value of the job */
				if ((phook != NULL) &&
				    (phook->user == HOOK_PBSUSER)) {
					long dval = atol(data_value);
					if (index == JOB_ATR_euser) {
						snprintf(log_buffer, sizeof(log_buffer),
							 "object '%s': ignoring setting attribute %s,"
							 " executing hook has user=pbsuser",
							 obj_name, name_str);
						log_err(-1, __func__, log_buffer);
						/* process a new line */
						line_data[0] = '\0';
						continue;
					} else if ((index == JOB_ATR_runcount) && (is_jattr_set(pjob2, index)) && (dval < get_jattr_long(pjob2, index))) {
						snprintf(log_buffer, sizeof(log_buffer),
							 "object '%s': ignoring setting attribute %s,"
							 " executing hook has user=pbsuser, "
							 " cannot decrease value from %ld to %ld",
							 obj_name, name_str,
							 get_jattr_long(pjob2, index),
							 dval);
						log_err(-1, __func__, log_buffer);
						/* process a new line */
						line_data[0] = '\0';
						continue;
					}
				}

				/* decode attribute */
				errcode = set_jattr_generic(pjob2, index, data_value, resc_str, INTERNAL);
				/* unknown resources still get decoded */
				/* using "unknown" placeholder resc def */
				if ((errcode != 0) &&
				    (errcode != PBSE_UNKRESC)) {
					snprintf(log_buffer, sizeof(log_buffer),
						 "object '%s' failed to decode (%s,%s,%s)! (errorcode %d)",
						 obj_name, name_str, resc_str ? resc_str : "",
						 data_value, errcode);
					log_err(-1, __func__, log_buffer);
					/* process a new line */
					line_data[0] = '\0';
					continue;
				}
				if (errcode == PBSE_UNKRESC) {
					resource *prsc;
					resource_def *prdef;
					svrattrl *plist, *plist2, *plist_next;

					prdef = &svr_resc_def[RESC_UNKN];
					prsc = find_resc_entry(get_jattr(pjob2, index), prdef);

					if ((prdef == NULL) || (prsc == NULL)) {
						log_err(-1, __func__, "bad unknown resc");
						/* process a new line */
						line_data[0] = '\0';
						continue;
					}

					plist = (svrattrl *) GET_NEXT(prsc->rs_value.at_val.at_list);

					do {
						if (plist == NULL)
							break;

						plist_next = (svrattrl *) GET_NEXT(plist->al_link);

						/* check for duplicate resource */
						/* entry. The later ones take */
						/* precedence */
						plist2 = (svrattrl *) GET_NEXT(plist->al_link);
						while (plist2 != NULL) {
							if (strcmp(plist->al_resc,
								   plist2->al_resc) == 0) {
								delete_link(&plist->al_link);
								free(plist);
								break;
							}
							plist2 = (svrattrl *) GET_NEXT(plist2->al_link);
						}

						plist = plist_next;
					} while (plist != NULL);
				}
				/* resources set in a hook should be flagged */
				/* as such (see how mom_set_use treat */
				/* ATR_VFLAG_HOOK set resources) */
				if (resc_str != NULL) { /* a resource */
					resource_def *rd;
					resource *pres;

					rd = find_resc_def(svr_resc_def, resc_str);
					if (rd != NULL) {
						pres = find_resc_entry(
							get_jattr(pjob2, index),
							rd);
						if (pres != NULL) {
							pres->rs_value.at_flags |=
								ATR_VFLAG_HOOK;
						}
					}
				}
				/* attributes in a hook should be flagged */
				/* with ATR_VFLAG_HOOK                    */
				(get_jattr(pjob2, index))->at_flags |= ATR_VFLAG_HOOK;
			}
		}
		if ((strncmp(obj_name, EVENT_VNODELIST_FAIL_OBJECT,
			     vn_fail_obj_len) == 0) ||
		    (strncmp(obj_name, EVENT_VNODELIST_OBJECT,
			     vn_obj_len) == 0)) {

			int *start_new_vnl_p;
			vnl_t **hvnlp_p = NULL;

			if (strncmp(obj_name,
				    EVENT_VNODELIST_FAIL_OBJECT,
				    vn_fail_obj_len) == 0) {
				start_new_vnl_p = &start_new_vnl_fail;
				hvnlp_p = &hvnlp_fail;
			} else {
				start_new_vnl_p = &start_new_vnl;
				hvnlp_p = &hvnlp;
			}

			/* NOTE: obj_name here is: pbs.event().vnode_list[<vname>] */

			/* important here to look for the leftmost '[' (using strchr)
			 * and the rightmost ']' (using strrchr)
			 * as we can have:
			 *	pbs.event().vnode_list["altix[5]"].<attr>=<val>
			 * 	and "altix[5]" is a valid vnode id.
			 */
			if (((pc1 = strchr(obj_name, '[')) != NULL) &&
			    ((pc2 = strrchr(obj_name, ']')) != NULL) &&
			    (pc2 > pc1)) {
				pc1++;	     /*  pc1=<vname>] */
				*pc2 = '\0'; /* pc1=<vname>  */
				pc2++;

				/* now let's if there's anything quoted inside */
				pc3 = strchr(pc1, '"');
				if (pc3 != NULL)
					pc4 = strchr(pc3 + 1, '"');
				else
					pc4 = NULL;

				if (pc3 && pc4 && (pc4 > pc3)) {
					pc3++;
					*pc4 = '\0';
					vname_str = pc3;
				} else {
					vname_str = pc1;
				}
			} else {
				snprintf(log_buffer, sizeof(log_buffer),
					 "object '%s' does not have a vnode name!",
					 obj_name);
				log_err(-1, __func__, log_buffer);
				/* process a new line */
				line_data[0] = '\0';
				continue;
			}

			if ((name_str != NULL) && (resc_str != NULL)) {
				/* format is in in_data: <attrib>\0<resc_str> */
				*(resc_str - 1) = '.'; /* so name_str=<attrib>.<resc_str> */
			}
			/* Found: resc_str = <resc_name>,<value_type> */
			if (resc_str != NULL) {
				if ((pc5 = strchr(resc_str, ',')) != NULL) {
					*pc5 = '\0';
					pc5++;
					value_type = pc5;
				}
			}

			if (*start_new_vnl_p) {
				*start_new_vnl_p = 0;
				/* now add new hook_vnl_action structure to */
				/* the list of such to return for updating  */
				/* the Server                               */
				pvna = malloc(sizeof(struct hook_vnl_action));
				if ((pvna != NULL) && (pvnalist != NULL)) {
					CLEAR_LINK(pvna->hva_link);
					snprintf(pvna->hva_euser, sizeof(pvna->hva_euser),
						 "%s", hook_euser);
					pvna->hva_actid = 0;
					pvna->hva_vnl = NULL;
					pvna->hva_update_cmd = IS_UPDATE_FROM_HOOK;
					if (strncmp(obj_name, EVENT_VNODELIST_FAIL_OBJECT, vn_fail_obj_len) == 0) {
						pvna->hva_update_cmd = IS_UPDATE_FROM_HOOK2;
					}
					if (vnl_alloc(hvnlp_p) == NULL) {
						log_err(errno, __func__, "Failed to allocate hvnlp");
					} else {
						pvna->hva_vnl = *hvnlp_p;
						append_link(pvnalist,
							    &pvna->hva_link, pvna);
					}
				} else {
					log_err(errno, __func__, "Failed to allocate hook action");
				}
				if ((copy_file == 0) && (*hvnlp_p != NULL)) {
					/* add a entry to the vnl for the user */
					rc = vn_addvnr(*hvnlp_p, mom_short_name,
						       VNATTR_HOOK_REQUESTOR,
						       hook_euser,
						       ATR_TYPE_STR, READ_ONLY,
						       NULL);
					if (rc == -1) {
						snprintf(log_buffer, sizeof(log_buffer),
							 "Failed to add '%s %s=%s' vnode resource copy_file=%d",
							 mom_short_name, VNATTR_HOOK_REQUESTOR,
							 hook_euser, copy_file);
						log_err(-1, __func__, log_buffer);
					}
				}
			}
			rc = -1;
			if (*hvnlp_p) {
				char *p2;
				resource_def *prdef;
				unsigned int at_type;
				unsigned int rs_flag;

				/* We're now passing type and flag so that if 'name_str' resource */
				/* does not exist, then it will be dynamically allocated on the server */
				/* side, essentially allowing hook scripts to define custom resource! */
				rs_flag = READ_WRITE | ATR_DFLAG_CVTSLT | ATR_DFLAG_MOM;
				if ((p2 = strrchr(name_str, '.')) != NULL) {
					p2++;
					prdef = find_resc_def(svr_resc_def, p2);
					if (prdef == NULL) { /* a custom resource */
						if (value_type != NULL) {
							if (strcmp(value_type, "boolean") == 0) {
								at_type = ATR_TYPE_BOOL;
							} else if (strcmp(value_type, "long") == 0) {
								at_type = ATR_TYPE_LONG;
							} else if (strcmp(value_type, "size") == 0) {
								at_type = ATR_TYPE_SIZE;
							} else if (strcmp(value_type, "float") == 0) {
								at_type = ATR_TYPE_FLOAT;
							} else if (strcmp(value_type, "string_array") == 0) {
								at_type = ATR_TYPE_ARST;
							} else {
								at_type = ATR_TYPE_STR;
							}
						} else {
							at_type = ATR_TYPE_STR;
						}
					} else {
						at_type = prdef->rs_type;
						rs_flag = prdef->rs_flags;
					}
				} else {
					at_type = ATR_TYPE_STR;
				}
				rc = vn_addvnr(*hvnlp_p, vname_str, name_str,
					       data_value, at_type, rs_flag, NULL);
				if (rc == -1) {
					snprintf(log_buffer, sizeof(log_buffer),
						 "Failed to add '%s %s=%s' at_type=%d rs_flag=%d",
						 vname_str, name_str, data_value, at_type, rs_flag);
					log_err(-1, __func__, log_buffer);
				}
			}

		} else if ((strcmp(obj_name, SERVER_OBJECT) == 0) &&
			   (strcmp(name_str, PY_SCHEDULER_RESTART_CYCLE_METHOD) == 0)) {

			if ((strcmp(data_value, "True") == 0) &&
			    (copy_file == 0)) {
				/* ask Server to tell Scheduler to restart a */
				/* scheduling cycle; only done from main Mom */
				if (send_sched_recycle(hook_euser) != 0) {
					/* process a new line */
					line_data[0] = '\0';
					continue;
				}
			}

		} else if (strcmp(obj_name, PBS_OBJ) == 0) {

			if ((reboot_flag != NULL) &&
			    (strcmp(name_str, PBS_REBOOT_OBJECT) == 0)) {
				if (strcmp(data_value, "True") == 0)
					*reboot_flag = 1;
				else
					*reboot_flag = 0;
			} else if ((reboot_cmd != NULL) &&
				   (strcmp(name_str,
					   PBS_REBOOT_CMD_OBJECT) == 0)) {
				pbs_strncpy(reboot_cmd, data_value, HOOK_BUF_SIZE);
			}
		}

		if ((fp2 != NULL) && (fputs(line_data, fp2) < 0)) {
			snprintf(log_buffer, sizeof(log_buffer),
				 "Failed to save data in file %s",
				 hook_job_outfile);
			log_err(errno, __func__, log_buffer);
			rc = 1;
			goto get_hook_results_end;
		}
		line_data[0] = '\0';
	}

	if (found_joblist && (found_rerunjob_action || found_deletejob_action)) {
		if ((reject_deletejob != NULL) && (*reject_deletejob)) {
			/* deletejob takes precedence */
			new_job_action_req(pjob2, phook ? phook->user : HOOK_PBSADMIN, JOB_ACT_REQ_DELETE);
		} else if ((reject_rerunjob != NULL) && (*reject_rerunjob)) {
			new_job_action_req(pjob2, phook ? phook->user : HOOK_PBSADMIN, JOB_ACT_REQ_REQUEUE);
		}
	}
	rc = 0;

get_hook_results_end:

	if (fp != NULL)
		fclose(fp);

	if (fp2 != NULL) {
		if (fflush(fp2) != 0) {
			/* error in writting job related hook results file */
			snprintf(log_buffer, sizeof(log_buffer),
				 "Failed to save data in file %s",
				 hook_job_outfile);
			log_err(errno, __func__, log_buffer);
			rc = 1;
			fclose(fp2);
			unlink(hook_job_outfile);
		} else {
			fclose(fp2);
		}
	}
	if (phook && !phook->debug) {
		(void) unlink(input_file);
	}
	if (line_data != NULL) {
		free(line_data);
	}
	if (in_data != NULL) {
		free(in_data);
	}

	return (rc);
}

/**
 * @brief
 *	Make the actual call to reboot the current host.
 *	If 'reboot_cmd' is NULL, then use the default reboot
 *	cmd line - see REBOOT_CMD macro.
 *
 * @param[in] reboot_cmd char pointer which hold cmd to rebbot host
 *
 * @return Void
 *
 */
static void
do_reboot(char *reboot_cmd)
{
	char bootcmd[HOOK_BUF_SIZE];
	int rcode;

	if ((reboot_cmd == NULL) || (*reboot_cmd == '\0'))
		pbs_strncpy(bootcmd, REBOOT_CMD, sizeof(bootcmd));
	else
		pbs_strncpy(bootcmd, reboot_cmd, sizeof(bootcmd));

	snprintf(log_buffer, sizeof(log_buffer), "issuing cmd %s", bootcmd);
	log_event(PBSEVENT_DEBUG3, 0,
		  LOG_INFO, "do_reboot", log_buffer);

#ifndef WIN32
	if ((rcode = system(bootcmd)) != -1)
#else
	if ((rcode = wsystem(bootcmd, INVALID_HANDLE_VALUE)) == 0)
#endif
	{
		log_event(PBSEVENT_DEBUG3, 0,
			  LOG_INFO, "do_reboot", "mom exiting");
		exit(0);
	} else {
		snprintf(log_buffer, sizeof(log_buffer),
			 "reboot failed exit code=%d", rcode);
		log_event(PBSEVENT_ERROR, 0,
			  LOG_ERR, __func__, log_buffer);
	}
}

/**
 * @brief
 *	Add a hook's delete or requeue action request to the list of such
 *	actions sent to server and call send_hook_job_action() to send it
 *	to the server.  It will be removed from the list when the Server
 *	replies or resent if the Server connection is reestablished.
 *
 * @param[in] pjob - pointer to job structure
 * @param[in] huser - hook ran as admin or user
 * @param[in] action - JOB_ACT_REQ_DELETE (1) or JOB_ACT_REQ_REQUEUE (0)
 *
 * @return none
 *
 */
void
new_job_action_req(job *pjob, enum hook_user huser, int action)
{
	struct hook_job_action *phja;

	if (pjob == NULL) {
		log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_JOB, LOG_ERR,
			  __func__, "Job received is NULL");
		return;
	}
	phja = malloc(sizeof(struct hook_job_action));
	if (phja == NULL) {
		log_err(PBSE_SYSTEM, __func__, msg_err_malloc);
		return;
	}
	CLEAR_LINK(phja->hja_link);
	snprintf(phja->hja_jid, sizeof(phja->hja_jid), "%s", pjob->ji_qs.ji_jobid);
	phja->hja_actid = ++hook_action_id;

	if (is_jattr_set(pjob, JOB_ATR_run_version))
		phja->hja_runct = get_jattr_long(pjob, JOB_ATR_run_version);
	else
		phja->hja_runct = get_jattr_long(pjob, JOB_ATR_runcount);

	phja->hja_huser = huser;
	phja->hja_action = action;
	append_link(&svr_hook_job_actions, &phja->hja_link, phja);
	send_hook_job_action(phja);
}

/**
 * @brief
 *	This function runs after the task that runs a single periodic hook
 *	in the background completes execution.  If there was an error (the
 *	hook process returned a non-zero exits status), the results file
 *	is just discarded (it may not even be there).
 *
 * @param[in] 	pwt - the work task.
 *
 * @return none
 *
 */
static void
post_periodic_hook(struct work_task *pwt)
{
	int wstat = pwt->wt_aux;
	hook *phook = (hook *) pwt->wt_parm1;
#ifdef WIN32
	pid_t mypid = pwt->wt_aux2;
#else
	pid_t mypid = pwt->wt_event;
#endif
	char hook_outfile[MAXPATHLEN + 1];
	char reject_msg[HOOK_MSG_SIZE + 1];
	time_t next_time;
	char *next_time_str;
	int accept_flag = 1;
	int hook_error_flag = 0;
	int reject_flag = 0;
	int rerun_flag = 0;
	int delete_flag = 0;
	int reboot_flag = 0;
	char reboot_cmd[HOOK_BUF_SIZE];
	pbs_list_head vnl_changes;

	CLEAR_HEAD(vnl_changes);
	reboot_cmd[0] = '\0';
	reject_msg[0] = '\0';

	/* Check hook exit status */
	if (wstat != 0) {
		snprintf(log_buffer, LOG_BUF_SIZE - 1,
			 "Non-zero exit status %d encountered for periodic hook",
			 wstat);
		log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_HOOK,
			  LOG_ERR, phook->hook_name, log_buffer);
		hook_error_flag = 1; /* hook results are invalid */
	}

	/* hook results path */
	snprintf(hook_outfile, MAXPATHLEN, FMT_HOOK_OUTFILE,
		 path_hooks_workdir,
		 HOOKSTR_EXECHOST_PERIODIC,
		 phook->hook_name, mypid);

	if (hook_error_flag == 0) {

		/* hook exited normally, get results from file  */
		if (get_hook_results(hook_outfile, &accept_flag, &reject_flag,
				     reject_msg, sizeof(reject_msg), &rerun_flag,
				     &delete_flag, &reboot_flag, reboot_cmd, HOOK_BUF_SIZE,
				     &vnl_changes, NULL, phook, 0, NULL) != 0) {
			log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_HOOK,
				  LOG_ERR, phook->hook_name,
				  "Failed getting hook results");
			/* error getting results, do not accept results */
			hook_error_flag = 1;
		}
	}

	if ((hook_error_flag == 1) || (accept_flag == 0)) {

		snprintf(log_buffer, sizeof(log_buffer),
			 "%s request rejected by '%s'",
			 "exechost_periodic", phook->hook_name);
		log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_HOOK,
			  LOG_ERR, phook->hook_name, log_buffer);
		if (reject_msg[0] != '\0') {
			snprintf(log_buffer, sizeof(log_buffer), "%s",
				 reject_msg);
			/* log also the custom reject message */
			log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_HOOK,
				  LOG_ERR, phook->hook_name, log_buffer);
		}
	}

	if (hook_error_flag == 0) {
		/* No hook error means data is communicated to */
		/* the server and actions are done to jobs.    */
		log_event(PBSEVENT_DEBUG3, PBS_EVENTCLASS_HOOK,
			  LOG_INFO, phook->hook_name, "periodic hook accepted");

		/* remove the processed results file, note that if  */
		/* there was an error, it is left for debugging use */
		if (phook && !phook->debug)
			(void) unlink(hook_outfile); /* remove file */

		if ((struct hook_vnl_action *) GET_NEXT(vnl_changes) != NULL) {

			/* there are vnode hook updates */
			/* Push hook changes to server */

			hook_requests_to_server(&vnl_changes);
		}

		if (reboot_flag) {
			log_event(PBSEVENT_DEBUG, PBS_EVENTCLASS_HOOK,
				  LOG_INFO, phook->hook_name,
				  "requested for host to be rebooted");
			do_reboot(reboot_cmd);
		}
	}
	vna_list_free(vnl_changes); /* free the list of changes */

	next_time = time_now + phook->freq;
	next_time_str = ctime(&next_time);
	if ((next_time_str != NULL) && (next_time_str[0] != '\0')) {
		next_time_str[strlen(next_time_str) - 1] = '\0'; /* rem newline */
		snprintf(log_buffer, sizeof(log_buffer), "will run on %s",
			 next_time_str);
		log_event(PBSEVENT_DEBUG3, PBS_EVENTCLASS_HOOK,
			  LOG_ERR, phook->hook_name, log_buffer);
	}

	(void) set_task(WORK_Timed, time_now + phook->freq,
			run_periodic_hook_bg_task, phook);
}

/**
 *
 *	Based on 'hook_fail_action', send a request to server to
 *	perform a hook fail action for hook named 'hook_name'.
 *
 * @param[in]	hook_name - hook in question
 * @param[in]	hook_fail_action - the actual hook fail action.
 *
 * @return void
 */
void
send_hook_fail_action(hook *phook)
{

	char hook_buf[HOOK_BUF_SIZE];
	vnl_t *tvnl = NULL;
	int vret = -1;

	if ((phook == NULL) || (phook->hook_name == NULL)) {
		log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_HOOK,
			  LOG_ERR, __func__, "Hook received is NULL");
		return;
	}

	snprintf(hook_buf, sizeof(hook_buf), "1,%s", phook->hook_name);

	if (vnl_alloc(&tvnl) == NULL) {
		snprintf(log_buffer, sizeof(log_buffer),
			 "Failed to vnl_alloc vnlp for %s",
			 phook->hook_name);
		log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_HOOK,
			  LOG_INFO, "", log_buffer);
		goto send_hook_fail_action_error;
	}

	if (phook->fail_action & HOOK_FAIL_ACTION_OFFLINE_VNODES) {
		vret = vn_addvnr(tvnl, mom_short_name,
				 VNATTR_HOOK_OFFLINE_VNODES,
				 hook_buf, 0, 0, NULL);

		if (vret != 0) {
			snprintf(log_buffer, sizeof(log_buffer),
				 "Failed to add to vnlp: %s=%s",
				 VNATTR_HOOK_OFFLINE_VNODES, hook_buf);
			log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_HOOK,
				  LOG_INFO, phook->hook_name, log_buffer);
			goto send_hook_fail_action_error;
		}
	}

	if (phook->fail_action & HOOK_FAIL_ACTION_SCHEDULER_RESTART_CYCLE) {
		vret = vn_addvnr(tvnl, mom_short_name,
				 VNATTR_HOOK_SCHEDULER_RESTART_CYCLE,
				 hook_buf, 0, 0, NULL);
		if (vret != 0) {
			snprintf(log_buffer, sizeof(log_buffer),
				 "Failed to add to vnlp: %s=%s",
				 VNATTR_HOOK_SCHEDULER_RESTART_CYCLE,
				 hook_buf);
			log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_HOOK,
				  LOG_INFO, phook->hook_name, log_buffer);
			goto send_hook_fail_action_error;
		}
	}

	if (vret == 0) {
		/* this saves 'tvnl' in svr_vnl_action, and later freed
		 * upon server acking action
		 */
		(void) send_hook_vnl(tvnl);
		tvnl = NULL;
	}

send_hook_fail_action_error:
	if (tvnl != NULL)
		vnl_free(tvnl);
}

/**
 *
 * @brief
 *	Record the name of the last hook that executed
 *	on behalf of 'pjob' into a well-known file
 *	location:
 *
 *	"<location_directory>/hook_<pjob's jobid>.out"
 *
 *
 * @param[in]	hook_event - calling event.
 * @param[in]	hook_name - name of hook that executed
 * @param[in] 	pjob - associated job executing hook
 * @param[in] 	filepath - name of a file whose
 *			directory location is used as
 *			<location_directory>.
 *
 * @note
 *	This will currently record only for
 *	HOOK_EVENT_EXECJOB_PROLOGUE hooks.
 * @return void
 */
static void
record_job_last_hook_executed(unsigned int hook_event,
			      char *hook_name, job *pjob, char *filepath)
{
	char hook_job_outfile[MAXPATHLEN + 1];
	FILE *fp = NULL;
	char *p;
	char chr_save = '\0';
	char *p_dir = NULL;

	if (pjob == NULL) {
		log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_HOOK,
			  LOG_ERR, __func__, "Job not received");
		return;
	}

	if (hook_name == NULL) {
		log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_HOOK,
			  LOG_ERR, __func__, "Hook not received");
		return;
	}
	if (hook_event != HOOK_EVENT_EXECJOB_PROLOGUE) {
		return;
	}

	if (filepath != NULL) {
		p = strrchr(filepath, '/');
		if (p != NULL) {
			p++;
			chr_save = *p;
			*p = '\0';
			p_dir = filepath;
		}
	} else {
		log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_HOOK,
			  LOG_ERR, __func__, "Hook not received");
	}

	snprintf(hook_job_outfile, MAXPATHLEN,
		 FMT_HOOK_JOB_OUTFILE, p_dir ? p_dir : "",
		 pjob->ji_qs.ji_jobid);

	if (chr_save != '\0')
		*p = chr_save; /* restore */

	fp = fopen(hook_job_outfile, "w");
	if (fp == NULL) {
		snprintf(log_buffer, sizeof(log_buffer),
			 "failed to open hook_job_outfile=%s",
			 hook_job_outfile);
		log_err(errno, __func__, log_buffer);
		return;
	}
	fprintf(fp, "%s=%s\n", PY_EVENT_HOOK_NAME,
		hook_name);
	fclose(fp);
}

/**
 * @brief
 * This function runs after execution of a single hook and processes
 * the results from hook execution. If hook is backgrounded,on
 * successful execution, a new task will be created to run the next
 * hook script and if there was an error (the hook process returned a non-zero exit
 * status) it does not create the new task for the next hook script.
 *
 * @param[in] 	ptask - the work task.
 *
 * @return 1 a hook accepted
 * @return 0 a hook rejected
 * @return -1 an internal error occurred
 */
int
post_run_hook(struct work_task *ptask)
{

	int accept_flag = 1;
	int reject_flag = 0;
	int reject_rerunjob = 0;
	int reject_deletejob = 0;
	int reboot_flag = 0;
	int log_type = 0;
	int log_class = 0;
	int hook_error_flag = 0;
	int *reject_errcode = NULL;
	int wstat = 0;
	char *log_id = NULL;
	char reject_msg[HOOK_MSG_SIZE + 1] = {'\0'};
	char reboot_cmd[HOOK_BUF_SIZE] = {'\0'};
	char hook_outfile[MAXPATHLEN + 1] = {'\0'};
	hook *phook = NULL;
	mom_hook_input_t *hook_input = NULL;
	job *pjob = NULL;
	struct work_task *new_task = NULL;
	pbs_list_head vnl_changes;
	mom_process_hooks_params_t *php = NULL;

	if (ptask == NULL) {
		log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_HOOK,
			  LOG_ERR, __func__, "missing ptask argument to event");
		return -1;
	}

	if ((phook = (hook *) ptask->wt_parm1) == NULL) {
		log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_HOOK,
			  LOG_ERR, __func__, "missing hook phook argument to event");
		return -1;
	}

	if ((php = (mom_process_hooks_params_t *) ptask->wt_parm2) == NULL) {
		log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_HOOK,
			  LOG_ERR, __func__, "missing hook params argument to event");
		return -1;
	}

	if (php->hook_event == HOOK_EVENT_EXECHOST_PERIODIC) {
		free(php);
		post_periodic_hook(ptask);
		return 1;
	}

	if ((hook_input = (mom_hook_input_t *) php->hook_input) == NULL) {
		log_event(PBSEVENT_ERROR, PBS_EVENTCLASS_HOOK,
			  LOG_ERR, __func__, "missing input argument to event");
		return -1;
	}

	pjob = (job *) hook_input->pjob;
	CLEAR_HEAD(vnl_changes);

	if ((php->hook_event == HOOK_EVENT_EXECHOST_PERIODIC) ||
	    (php->hook_event == HOOK_EVENT_EXECHOST_STARTUP)) {
		log_id = phook->hook_name;
		log_type = PBSEVENT_DEBUG2;
		log_class = PBS_EVENTCLASS_HOOK;
	} else {
		log_id = pjob->ji_qs.ji_jobid;
		log_type = PBSEVENT_JOB;
		log_class = PBS_EVENTCLASS_JOB;
	}

	/* hook results path */
	snprintf(hook_outfile, MAXPATHLEN, FMT_HOOK_OUTFILE,
		 path_hooks_workdir,
		 hook_event_as_string(php->hook_event),
		 phook->hook_name,
		 (pid_t) ((php->parent_wait) ? php->child : ptask->wt_event));

	if (php->parent_wait == 0) {
		/* background hook */
		wstat = ptask->wt_aux;

		log_event(PBSEVENT_DEBUG3, PBS_EVENTCLASS_HOOK,
			  LOG_INFO, phook->hook_name, "finished");

		switch (wstat) {
			case 0:
				break;
			case -2: /* unhandled exception return on Windows */
			case 254:
				snprintf(log_buffer, LOG_BUF_SIZE - 1,
					 "%s hook '%s' encountered an exception, "
					 "request rejected",
					 hook_event_as_string(php->hook_event), phook->hook_name);
				log_event(log_type, log_class,
					  LOG_ERR, log_id, log_buffer);
				record_job_last_hook_executed(php->hook_event,
							      phook->hook_name, pjob, hook_outfile);
				hook_error_flag = 1;
				break;
				/* -3 return from pbs_python == 2^8-3, but run_hook() */
				/* itself could return "-3" if it catches the alarm()   */
				/* to the process first. Both run_hook() code here, and */
				/* pbs_python program set alarm signals. One or the   */
				/* other would catch it first */
			case -3: /* somewhere in this file, we do return -3 */
			case 253:
				snprintf(log_buffer, LOG_BUF_SIZE - 1,
					 "alarm call while running %s hook '%s', "
					 "request rejected",
					 hook_event_as_string(php->hook_event), phook->hook_name);
				log_event(log_type, log_class, LOG_ERR, log_id,
					  log_buffer);
				record_job_last_hook_executed(php->hook_event,
							      phook->hook_name, pjob, hook_outfile);
				hook_error_flag = 1;
				break;
			default:
				snprintf(log_buffer, LOG_BUF_SIZE - 1,
					 "Non-zero exit status %d encountered for %s hook",
					 wstat, hook_event_as_string(php->hook_event));
				log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_HOOK,
					  LOG_ERR, phook->hook_name, log_buffer);
				hook_error_flag = 1; /* hook results are invalid */
		}
	}

	if (hook_error_flag == 0) {
		/* hook exited normally, get results from file  */
		if (get_hook_results(hook_outfile, &accept_flag, &reject_flag,
				     reject_msg, sizeof(reject_msg), &reject_rerunjob,
				     &reject_deletejob, &reboot_flag, reboot_cmd,
				     HOOK_BUF_SIZE, &vnl_changes, pjob, phook,
				     (php->hook_event == HOOK_EVENT_EXECHOST_STARTUP) ? 0 : !php->update_svr,
				     php->hook_output) != 0) {
			log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_HOOK,
				  LOG_ERR, phook->hook_name,
				  "Failed to get hook results");
			vna_list_free(vnl_changes);
			if (php->parent_wait)
				return -1;
			hook_error_flag = 1;
		}
	}

	if (!hook_error_flag) {
		if ((php->hook_output != NULL) && (php->hook_output->vnl != NULL)) {
			struct hook_vnl_action *phvna;
			/* save vnl changes into  results array */
			for (phvna = GET_NEXT(vnl_changes); phvna;
			     phvna = GET_NEXT(phvna->hva_link)) {
				vn_merge(php->hook_output->vnl, phvna->hva_vnl, NULL);
			}
		}

		if (php->update_svr == 1) {
			if (pjob != NULL) {
				/* Delete job or reject job actions */
				/* NOTE: Must appear here before vnode changes, */
				/* since this action will be sent whether or not */
				/* hook script executed by PBSADMIN or not. */
				if (reject_deletejob) {
					/* deletejob takes precedence */
					new_job_action_req(pjob, phook->user, JOB_ACT_REQ_DELETE);
				} else if (reject_rerunjob) {
					new_job_action_req(pjob, phook->user, JOB_ACT_REQ_REQUEUE);
				}

				/* Whether or not we accept or reject, we'll make */
				/* job changes, vnode changes, job actions */
				enqueue_update_for_send(pjob, IS_RESCUSED_FROM_HOOK);
			}

			if (vnl_changes.ll_next != NULL)
				/* Push vnl hook changes to server */
				hook_requests_to_server(&vnl_changes);
		} else {
			vna_list_free(vnl_changes);
		}
	}

	/* reject if at least one hook script rejects */
	if (hook_error_flag || !accept_flag) {

		if (php->hook_msg != NULL) {
			snprintf(php->hook_msg, php->msg_len - 1,
				 "%s request rejected by '%s'",
				 hook_event_as_string(php->hook_event),
				 phook->hook_name);
			log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_HOOK,
				  LOG_ERR, phook->hook_name, php->hook_msg);
		} else {
			snprintf(log_buffer, sizeof(log_buffer),
				 "%s request rejected by '%s'",
				 hook_event_as_string(php->hook_event),
				 phook->hook_name);
			log_event(PBSEVENT_DEBUG2, PBS_EVENTCLASS_HOOK,
				  LOG_ERR, phook->hook_name, log_buffer);
		}

		if (reject_msg[0] != '\0') {
			if (php->hook_msg != NULL) {
				snprintf(php->hook_msg, php->msg_len - 1, "%s",
					 reject_msg);
				/* log also the custom reject message */
				log_event(log_type,
					  log_class, LOG_ERR,
					  log_id, php->hook_msg);
			} else {
				snprintf(log_buffer, sizeof(log_buffer), "%s",
					 reject_msg);
				/* log also the custom reject message */
				log_event(log_type, log_class, LOG_ERR,
					  log_id, log_buffer);
			}
		}

		if (php->hook_output)
			reject_errcode = php->hook_output->reject_errcode;

		if (reject_errcode != NULL) {
			*reject_errcode = PBSE_HOOK_REJECT;
			if (reject_rerunjob)
				*reject_errcode = PBSE_HOOK_REJECT_RERUNJOB;
			if (reject_deletejob)
				*reject_errcode = PBSE_HOOK_REJECT_DELETEJOB;
		}
		if (php->parent_wait)
			return (0); /* don't process anymore hooks on reject */
	}

	if (!hook_error_flag && reboot_flag) {
		if (phook->user == HOOK_PBSUSER) {
			log_event(PBSEVENT_DEBUG, PBS_EVENTCLASS_HOOK,
				  LOG_INFO, phook->hook_name,
				  "Not allowed to issue reboot if run as user");
		} else {
			log_event(PBSEVENT_DEBUG, PBS_EVENTCLASS_HOOK,
				  LOG_INFO, phook->hook_name,
				  "requested for host to be rebooted");
			do_reboot(reboot_cmd);
		}
	}

	if (!php->parent_wait) {
		/* Background hook */
		/* Create task to check and run next hook script */
		new_task = set_task(WORK_Immed, 0, (void *) mom_process_background_hooks, phook);
		if (!new_task)
			log_err(errno, __func__,
				"Unable to set task for mom_process_background_hooks");
		else
			new_task->wt_parm2 = (void *) php;
	}

	return 1;
}

/**
 * @brief
 * This function replies to the outstanding request after
 * execution of the hook event was in background.
 *
 * @param[in] pjob
 *
 * @return void
 */

void
reply_hook_bg(job *pjob)
{
	int n = 0;
	int ret = 0;
	char jobid[PBS_MAXSVRJOBID + 1] = {'\0'};
	job *pjob2 = NULL;
	long runver;
#if !MOM_ALPS
	struct batch_request *preq = pjob->ji_preq;
#endif

	if (pjob->ji_hook_running_bg_on == BG_IS_DISCARD_JOB) {
		/**
		 * IS_DISCARD_JOB can be received by sister node as well,
		 * when node fail requeue is activated
		 */
		n = get_jattr_long(pjob, JOB_ATR_run_version);
		strcpy(jobid, pjob->ji_qs.ji_jobid);

		del_job_resc(pjob); /* rm tmpdir, etc. */
		pjob->ji_hook_running_bg_on = BG_NONE;
		job_purge_mom(pjob);
		dorestrict_user();

		if ((ret = is_compose(server_stream, IS_DISCARD_DONE)) != DIS_SUCCESS)
			goto err;

		if ((ret = diswst(server_stream, jobid)) != DIS_SUCCESS)
			goto err;

		if ((ret = diswsi(server_stream, n)) != DIS_SUCCESS)
			goto err;

		dis_flush(server_stream);
		tpp_eom(server_stream);

	} else if (pjob->ji_qs.ji_svrflags & JOB_SVFLG_HERE) { /*MS*/
		switch (pjob->ji_hook_running_bg_on) {
			case BG_CHECKPOINT_ABORT:
				pjob->ji_hook_running_bg_on = BG_NONE;
				exiting_tasks = 1;
				term_job(pjob);
				break;
			case BG_PBS_BATCH_DeleteJob:
			case BG_PBSE_SISCOMM:
				if ((pjob->ji_numnodes == 1) || (pjob->ji_hook_running_bg_on == BG_PBSE_SISCOMM)) {
					del_job_resc(pjob); /* rm tmpdir, etc. */
					pjob->ji_preq = NULL;
					(void) kill_job(pjob, SIGKILL);
					dorestrict_user();
#if !MOM_ALPS
					/*
					* The delete job request from Server will have
					* been or will be replied to and freed by the
					* alps_cancel_reservation code in the sequence
					* of functions started with the above call to
					* del_job_resc().
					*/
					if (pjob->ji_numnodes == 1)
						reply_ack(preq);
					else if (pjob->ji_hook_running_bg_on == BG_PBSE_SISCOMM)
						req_reject(PBSE_SISCOMM, 0, preq); /* sis down */
#endif
					job_purge_mom(pjob);
				} else
					pjob->ji_hook_running_bg_on = BG_NONE;
				/*
				* otherwise, job_purge() and dorestrict_user() are called in
				* mom_comm when all the sisters have replied. The reply to
				* the Server is also done there
				*/

			/**
			 * Following cases to avoid the below compilation
			 * error: enumeration value not handled in switch
			 */
			case BG_NONE:
			case BG_IM_DELETE_JOB_REPLY:
			case BG_IM_DELETE_JOB:
			case BG_IM_DELETE_JOB2:
			case BG_IS_DISCARD_JOB:
				break;
		}
	} else { /*SISTER MOM*/
		switch (pjob->ji_hook_running_bg_on) {
			case BG_CHECKPOINT_ABORT:
				pjob->ji_hook_running_bg_on = BG_NONE;
				exiting_tasks = 1;
				term_job(pjob);
				break;
			case BG_IM_DELETE_JOB_REPLY:
				post_reply(pjob, 0);
			case BG_IM_DELETE_JOB:
				pjob->ji_hook_running_bg_on = BG_NONE;
				mom_deljob(pjob);
				break;
			case BG_IM_DELETE_JOB2:
				strcpy(jobid, pjob->ji_qs.ji_jobid);
				pjob->ji_hook_running_bg_on = BG_NONE;
				if (is_jattr_set(pjob, JOB_ATR_run_version))
					runver = get_jattr_long(pjob, JOB_ATR_run_version);
				else
					runver = get_jattr_long(pjob, JOB_ATR_runcount);

				mom_deljob(pjob);

				/* Needed to create a lightweight copy of the job to
				 * contain only the jobid info, so I can just call
				 * new_job_action() to create a JOB_ACT_REQ_DEALLOCATE
				 * request. Can't use the original 'pjob' structure as
				 * before creating the request, the real job should have
				 * been deleted already.
				 */
				if ((pjob2 = job_alloc()) != NULL) {
					snprintf(pjob2->ji_qs.ji_jobid, sizeof(pjob2->ji_qs.ji_jobid), "%s", jobid);
					set_jattr_l_slim(pjob2, JOB_ATR_run_version, runver, SET);
					/* JOB_ACT_REQ_DEALLOCATE request will tell the
					 * the server that this mom has completely deleted the
					 * job and now the server can officially free up the
					 * job from the nodes managed by this mom, allowing
					 * other jobs to run.
					 */
					new_job_action_req(pjob2, HOOK_PBSADMIN, JOB_ACT_REQ_DEALLOCATE);
					job_free(pjob2);
				}
				break;

			/**
			 * Following cases to avoid the below compilation
			 * error: enumeration value not handled in switch
			 */
			case BG_NONE:
			case BG_PBS_BATCH_DeleteJob:
			case BG_PBSE_SISCOMM:
			case BG_IS_DISCARD_JOB:
				break;
		}
	}
	return;
err:
	sprintf(log_buffer, "%s", dis_emsg[ret]);
	log_err(-1, __func__, log_buffer);
	tpp_close(server_stream);
}

/**
 * @brief
 * This function loops through the hook list,
 * and runs it in the background.
 *
 * @param[in] ptask - the work task.
 *
 * @retval void
 */
static void
mom_process_background_hooks(struct work_task *ptask)
{
	char hook_infile[MAXPATHLEN + 1] = {'\0'};
	char hook_outfile[MAXPATHLEN + 1] = {'\0'};
	char hook_datafile[MAXPATHLEN + 1] = {'\0'};
	hook *phook = NULL;
	job *pjob = NULL;
	mom_process_hooks_params_t *php = NULL;

	if (ptask == NULL) {
		log_err(-1, __func__, "missing ptask argument");
		return;
	}

	if ((phook = (hook *) ptask->wt_parm1) == NULL) {
		log_err(-1, __func__, "missing phook argument");
		return;
	}

	if ((php = (mom_process_hooks_params_t *) ptask->wt_parm2) == NULL) {
		log_err(-1, __func__, "missing php argument");
		return;
	}

	pjob = php->hook_input->pjob;

	if (pjob->ji_bg_hook_task)
		pjob->ji_bg_hook_task = NULL;

	if (php->hook_output && *(php->hook_output->reject_errcode)) {
		reply_hook_bg(pjob);
		goto fini;
	}

	phook = (hook *) GET_NEXT(phook->hi_execjob_end_hooks);
	while (phook) {
		if (phook->enabled == FALSE) {
			phook = (hook *) GET_NEXT(phook->hi_execjob_end_hooks);
			continue;
		}
		if (phook->script == NULL) {
			log_event(PBSEVENT_DEBUG3, PBS_EVENTCLASS_HOOK,
				  LOG_ERR, phook->hook_name,
				  "Hook has no script content. Skipping hook.");
			phook = (hook *) GET_NEXT(phook->hi_execjob_end_hooks);
			continue;
		}
		break;
	}
	if (!phook) {
		reply_hook_bg(pjob);
		goto fini;
	}

	run_hook(phook, php->hook_event, php->hook_input,
		 php->req_user, php->req_host, 0, (void *) post_run_hook,
		 hook_infile, hook_outfile, hook_datafile, MAXPATHLEN + 1, php);
	return;

fini:
	if (php->hook_output) {
		free(php->hook_output->reject_errcode);
		free(php->hook_output);
	}
	free(php->hook_input);
	free(php);
}

/**
 * @brief
 *	Process hook scripts based on request type.
 *	This loops through the matching list of
 *	hooks, and executes the corresponding hook scripts.
 *
 * @param[in] 	hook_event - the event of the hooks that need to process.
 * @param[in] 	req_user - who requested for the hook to execute
 * @param[in] 	req_host - where the hook to execute is located
 * @param[in]	hook_input - struct of input parameters
 * @param[out]	hook_output - struct of output parameters
 * @param[in] 	hook_msg  - upon failure, if this buffer is set, fill it with
 *		the actual error message.
 * @param[in]   msg_len  - the size of 'hook_msg' buffer.
 * @param[in]	update_server - if true, send vnode and job attributes updates
 *			and/or requeue/delete job actions to the Server;
 *			done when NOT a child of Mom, but Mom herself.
 *			For exechost_startup hook, this must not be done,
 *			as the event has no job actions associated with the
 *			hook, and the vnode changes are sent separately
 *			upon mom acknowledging the HELLO server message.
 * @return	int
 * @retval 	0 means at least one hook was encountered to have rejected the
 *		request.
 * @retval 	1 means all the executed hooks have agreed to accept the request
 * @retval	2 means no hook script executed (special case).
 * @retval	-1 an internal error occurred
 * @retval	HOOK_RUNNING_IN_BACKGROUND
 * 				background process started for the hook script.
 *
 */
int
mom_process_hooks(unsigned int hook_event, char *req_user, char *req_host,
		  mom_hook_input_t *hook_input, mom_hook_output_t *hook_output, char *hook_msg,
		  size_t msg_len, int update_server)
{
	char hook_infile[MAXPATHLEN + 1];
	char hook_outfile[MAXPATHLEN + 1];
	char hook_datafile[MAXPATHLEN + 1];
	char *log_id = NULL;
	int rc;
	int num_run = 0;
	int set_job_exit = 0;
	int log_type = 0;
	int log_class = 0;
	int *reject_errcode = NULL;
	unsigned int *pfail_action = NULL;
	hook *phook;
	hook *phook_next = NULL;
	hook **last_phook = NULL;
	job *pjob = NULL;
	pbs_list_head vnl_changes;
	pbs_list_head *head_ptr;
	mom_process_hooks_params_t *php = NULL;
	struct work_task task;
	char perf_label[MAXBUFLEN];

	if (hook_input == NULL) {
		log_err(-1, __func__, "missing input argument to event");
		return (-1);
	}

	pjob = hook_input->pjob;

	/* If output objects are given, must have at least 3 */
	/* otherwise, input array is ignored */
	if (hook_output != NULL) {
		reject_errcode = hook_output->reject_errcode;
		last_phook = hook_output->last_phook;
		pfail_action = hook_output->fail_action;
	}
	if ((php = (mom_process_hooks_params_t *) malloc(
		     sizeof(mom_process_hooks_params_t))) == NULL) {
		log_err(errno, __func__, MALLOC_ERR_MSG);
		return -1;
	}
	php->hook_event = hook_event;
	php->req_user = req_user;
	php->req_host = req_host;
	php->hook_msg = hook_msg;
	php->msg_len = msg_len;
	php->update_svr = update_server;
	php->hook_input = hook_input;
	php->hook_output = hook_output;
	php->parent_wait = 1;

	CLEAR_HEAD(vnl_changes);
	switch (hook_event) {

		case HOOK_EVENT_EXECJOB_BEGIN:
			head_ptr = &svr_execjob_begin_hooks;
			break;
		case HOOK_EVENT_EXECJOB_PROLOGUE:
			head_ptr = &svr_execjob_prologue_hooks;
			break;
		case HOOK_EVENT_EXECJOB_EPILOGUE:
			head_ptr = &svr_execjob_epilogue_hooks;
			break;
		case HOOK_EVENT_EXECJOB_END:
			head_ptr = &svr_execjob_end_hooks;
#ifndef WIN32
			php->parent_wait = 0;
#endif
			break;
		case HOOK_EVENT_EXECJOB_PRETERM:
			head_ptr = &svr_execjob_preterm_hooks;
			break;
		case HOOK_EVENT_EXECJOB_LAUNCH:
			head_ptr = &svr_execjob_launch_hooks;
			break;
		case HOOK_EVENT_EXECHOST_PERIODIC:
			head_ptr = &svr_exechost_periodic_hooks;
			php->parent_wait = 0;
			break;
		case HOOK_EVENT_EXECHOST_STARTUP:
			head_ptr = &svr_exechost_startup_hooks;
			break;
		case HOOK_EVENT_EXECJOB_ATTACH:
			head_ptr = &svr_execjob_attach_hooks;
			break;
		case HOOK_EVENT_EXECJOB_RESIZE:
			head_ptr = &svr_execjob_resize_hooks;
			break;
		case HOOK_EVENT_EXECJOB_ABORT:
			head_ptr = &svr_execjob_abort_hooks;
			break;
		case HOOK_EVENT_EXECJOB_POSTSUSPEND:
			head_ptr = &svr_execjob_postsuspend_hooks;
			break;
		case HOOK_EVENT_EXECJOB_PRERESUME:
			head_ptr = &svr_execjob_preresume_hooks;
			break;
		default:
			free(php);
			return (-1); /* unexpected event encountered */
	}

	if (hook_msg != NULL)
		memset(hook_msg, '\0', msg_len);

	for (phook = (hook *) GET_NEXT(*head_ptr); phook; phook = phook_next) {
		switch (hook_event) {

			case HOOK_EVENT_EXECJOB_BEGIN:
				phook_next = (hook *) GET_NEXT(phook->hi_execjob_begin_hooks);
				break;
			case HOOK_EVENT_EXECJOB_PROLOGUE:
				phook_next = (hook *) GET_NEXT(phook->hi_execjob_prologue_hooks);
				break;
			case HOOK_EVENT_EXECJOB_EPILOGUE:
				phook_next = (hook *) GET_NEXT(phook->hi_execjob_epilogue_hooks);
				break;
			case HOOK_EVENT_EXECJOB_END:
				phook_next = (hook *) GET_NEXT(phook->hi_execjob_end_hooks);
				break;
			case HOOK_EVENT_EXECJOB_PRETERM:
				phook_next = (hook *) GET_NEXT(phook->hi_execjob_preterm_hooks);
				break;
			case HOOK_EVENT_EXECJOB_LAUNCH:
				phook_next = (hook *) GET_NEXT(phook->hi_execjob_launch_hooks);
				break;
			case HOOK_EVENT_EXECHOST_PERIODIC:
				phook_next = (hook *) GET_NEXT(phook->hi_exechost_periodic_hooks);
				break;
			case HOOK_EVENT_EXECHOST_STARTUP:
				phook_next = (hook *) GET_NEXT(phook->hi_exechost_startup_hooks);
				break;
			case HOOK_EVENT_EXECJOB_ATTACH:
				phook_next = (hook *) GET_NEXT(phook->hi_execjob_attach_hooks);
				break;
			case HOOK_EVENT_EXECJOB_RESIZE:
				phook_next = (hook *) GET_NEXT(phook->hi_execjob_resize_hooks);
				break;
			case HOOK_EVENT_EXECJOB_ABORT:
				phook_next = (hook *) GET_NEXT(phook->hi_execjob_abort_hooks);
				break;
			case HOOK_EVENT_EXECJOB_POSTSUSPEND:
				phook_next = (hook *) GET_NEXT(phook->hi_execjob_postsuspend_hooks);
				break;
			case HOOK_EVENT_EXECJOB_PRERESUME:
				phook_next = (hook *) GET_NEXT(phook->hi_execjob_preresume_hooks);
				break;
			default:
				free(php);
				return (-1); /*  should not get here */
		}

		if (phook->enabled == FALSE)
			continue;

		if (phook->script == NULL) {
			log_event(PBSEVENT_DEBUG3, PBS_EVENTCLASS_HOOK,
				  LOG_ERR, phook->hook_name,
				  "Hook has no script content. Skipping hook.");
			continue;
		}

		log_event(PBSEVENT_DEBUG3, PBS_EVENTCLASS_HOOK,
			  LOG_INFO, phook->hook_name, "started");

		hook_infile[0] = '\0';
		hook_outfile[0] = '\0';
		hook_datafile[0] = '\0';

		/* on an execjob_end or execjob_epilogue hook, need to set  */
		/* the job's exit value. 				    */
		/* Set it only once, and only if there's a hook to execute  */
		/* since we're affecting the job directly.		    */
		if (((hook_event == HOOK_EVENT_EXECJOB_END) ||
		     (hook_event == HOOK_EVENT_EXECJOB_EPILOGUE)) &&
		    !set_job_exit) {

			set_jattr_l_slim(pjob, JOB_ATR_exit_status, pjob->ji_qs.ji_un.ji_momt.ji_exitstat, SET);
			set_job_exit = 1;
		} else if ((hook_event == HOOK_EVENT_EXECJOB_LAUNCH) && (num_run >= 1)) {

			/*
			 * If there are multiple execjob_launch hooks,
			 * we need to cascade the execjob_launch specific
			 * parameters to the next execjob_launch hook,
			 * if any of the previous hooks has set these
			 * values.
			 */

			if (hook_output != NULL) {

				if (hook_output->progname != NULL) {
					hook_input->progname = *hook_output->progname;
				}

				if (hook_output->env != NULL) {
					hook_input->env = hook_output->env;
				}
				hook_input->argv = svrattrl_to_str_array(hook_output->argv);
			}
		}

		if (pjob != NULL)
			snprintf(perf_label, sizeof(perf_label), "hook_%s_%s_%s", hook_event_as_string(hook_event), phook->hook_name, pjob->ji_qs.ji_jobid);
		else
			snprintf(perf_label, sizeof(perf_label), "hook_%s_%s_%d", hook_event_as_string(hook_event), phook->hook_name, getpid());

		hook_perf_stat_start(perf_label, "mom_process_hooks", 1);
		rc = run_hook(phook, hook_event, hook_input,
			      req_user, req_host, php->parent_wait, (void *) post_run_hook,
			      hook_infile, hook_outfile, hook_datafile, MAXPATHLEN + 1, php);
		hook_perf_stat_stop(perf_label, "mom_process_hooks", 1);

		if (last_phook != NULL)
			*last_phook = phook;

		if (pfail_action != NULL)
			*pfail_action |= phook->fail_action;

		/* go back to mom's private directory */
		if (chdir(mom_home) != 0) {
			log_event(PBSEVENT_DEBUG2,
				  PBS_EVENTCLASS_HOOK, LOG_WARNING, phook->hook_name,
				  "unable to go back to mom_home");
		}

		log_event(PBSEVENT_DEBUG3, PBS_EVENTCLASS_HOOK, LOG_INFO, phook->hook_name, "finished");

		if ((hook_event == HOOK_EVENT_EXECHOST_PERIODIC) || (hook_event == HOOK_EVENT_EXECHOST_STARTUP)) {
			log_id = phook->hook_name;
			log_type = PBSEVENT_DEBUG2;
			log_class = PBS_EVENTCLASS_HOOK;
		} else {
			log_id = pjob->ji_qs.ji_jobid;
			log_type = PBSEVENT_JOB;
			log_class = PBS_EVENTCLASS_JOB;
		}

		switch (rc) {
			case 0: /* success */
				break;
				/* -2 return from pbs_python on Windows == 2^8-2 on Linux*/
			case -2:  /* unhandled exception return on Windows */
			case 254: /* unhandled exception return on Linux/Unix */
				snprintf(log_buffer, LOG_BUF_SIZE - 1,
					 "%s hook '%s' encountered an exception, "
					 "request rejected",
					 hook_event_as_string(hook_event), phook->hook_name);
				log_event(log_type, log_class,
					  LOG_ERR, log_id, log_buffer);
				if (hook_msg != NULL) {
					snprintf(hook_msg, msg_len - 1,
						 "request rejected as filter hook '%s' encountered an "
						 "exception. Please inform Admin",
						 phook->hook_name);
				}
				if (reject_errcode != NULL) {
					*reject_errcode = PBSE_HOOKERROR;
				}
				record_job_last_hook_executed(hook_event, phook->hook_name, pjob, hook_outfile);
				free(php);
				return (0);
				/* -3 return from pbs_python == 2^8-3, but run_hook() */
				/* itself could return "-3" if it catches the alarm()   */
				/* to the process first. Both run_hook() code here, and */
				/* pbs_python program set alarm signals. One or the   */
				/* other would catch it first */
			case -3:  /* somewhere in this file, we do return -3 */
			case 253: /* alarm timeout */
				snprintf(log_buffer, LOG_BUF_SIZE - 1,
					 "alarm call while running %s hook '%s', "
					 "request rejected",
					 hook_event_as_string(hook_event), phook->hook_name);
				log_event(log_type, log_class,
					  LOG_ERR, log_id, log_buffer);
				if (hook_msg != NULL) {
					snprintf(hook_msg, msg_len - 1,
						 "request rejected as filter hook '%s' got an "
						 "alarm call. Please inform Admin",
						 phook->hook_name);
				}
				if (reject_errcode != NULL) {
					*reject_errcode = PBSE_HOOKERROR;
				}
				record_job_last_hook_executed(hook_event, phook->hook_name, pjob, hook_outfile);
				free(php);
				return (0);
			default:
				snprintf(log_buffer, sizeof(log_buffer),
					 "Internal server error encountered. Skipping hook %s",
					 phook->hook_name);
				log_event(log_type, log_class,
					  LOG_ERR, log_id, log_buffer);
				free(php);
				return (-1); /* should not happen */
		}

		num_run++;

		if (hook_event == HOOK_EVENT_EXECHOST_PERIODIC) {
			/* hook backgrounded */
			if ((php = duplicate_php(php)) == NULL)
				return (-1);
			continue;
		}

		if (php->parent_wait == 0)
			return (HOOK_RUNNING_IN_BACKGROUND);

		task.wt_parm1 = (void *) phook;
		task.wt_parm2 = (void *) php;
		if ((rc = post_run_hook(&task)) != 1) {
			/* if a hook is not accepted do not proceed further*/
			free(php);
			return rc;
		}
	}
	if (num_run == 0) {
		free(php);
		return (2);
	}

	free(php);
	return (1);
}

/**
 * @brief
 * 	Cleans up files older than HOOKS_TMPFILE_MAX_AGE under
 *	<path_spool>.
 *
 * @param[in] 	ptask	- a task to queue up
 *
 * @return none
 *
 */
void
cleanup_hooks_in_path_spool(struct work_task *ptask)
{
	DIR *dir;
	struct dirent *pdirent;
	struct stat sbuf;
	char hook_file[MAXPATHLEN + 1];

	memset(hook_file, '\0', MAXPATHLEN + 1);
	dir = opendir(path_spool);
	if (dir == NULL) {
		sprintf(log_buffer, "could not opendir %s",
			path_hooks_workdir);
		log_err(errno, __func__, log_buffer);
		return;
	}
	while (errno = 0, (pdirent = readdir(dir)) != NULL) {

		if (pdirent->d_name[0] == '.') {
			if (pdirent->d_name[1] == '\0' ||
			    (pdirent->d_name[1] == '.' &&
			     pdirent->d_name[2] == '\0'))
				continue;
		}

		if (strncmp(pdirent->d_name, FMT_HOOK_PREFIX,
			    sizeof(FMT_HOOK_PREFIX) - 1) != 0)
			continue;

		snprintf(hook_file, MAXPATHLEN, "%s%s",
			 path_spool, pdirent->d_name);
		if (stat(hook_file, &sbuf) == -1) {
			sprintf(log_buffer, "could not stat %s", hook_file);
			log_err(errno, __func__, log_buffer);
			continue;
		}

		/* remove files older than 'HOOKS_TMPFILE_MAX_AGE' */
		if ((time_now - sbuf.st_ctime) > HOOKS_TMPFILE_MAX_AGE) {
			if (unlink(hook_file) < 0) {
				if (errno != ENOENT) {
					sprintf(log_buffer, "could not cleanup %s",
						hook_file);
					log_err(errno, __func__, log_buffer);
				}
			}
		}
	}
	if (errno != 0 && errno != ENOENT) {
		log_err(errno, __func__, "readdir");
	}
	if (dir) {
		(void) closedir(dir);
	}

	/*  cleanup of hooks temp files happen in the next */
	/* 'HOOKS_TMPFILE_NEXT_CLEANUP_PERIOD' secs.	   */
	(void) set_task(WORK_Timed, time_now + HOOKS_TMPFILE_NEXT_CLEANUP_PERIOD,
			cleanup_hooks_in_path_spool, NULL);
}

/**
 *  @brief
 *  	Initializes all the elements of mom_hook_input_t structure.
 *
 *  @param[in]	hook_input - the structure to initialize.
 *  @return void
 *
 */
void
mom_hook_input_init(mom_hook_input_t *hook_input)
{
	if (hook_input == NULL) {
		log_err(PBSE_HOOKERROR, __func__, "Hook input is NULL");
		return;
	}

	hook_input->pjob = NULL;
	hook_input->progname = NULL;
	hook_input->argv = NULL;
	hook_input->env = NULL;
	hook_input->vnl = NULL;
	hook_input->vnl_fail = NULL;
	hook_input->failed_mom_list = NULL;
	hook_input->succeeded_mom_list = NULL;
	hook_input->jobs_list = NULL;
}

/**
 *  @brief
 *  	Initializes all the elements of mom_hook_output_t structure.
 *
 *  @param[in]	hook_output - the structure to initialize.
 *  @return void
 *
 */
void
mom_hook_output_init(mom_hook_output_t *hook_output)
{
	if (hook_output == NULL) {
		log_err(PBSE_HOOKERROR, __func__, "Hook output is NULL");
		return;
	}

	hook_output->reject_errcode = NULL;
	hook_output->last_phook = NULL;
	hook_output->fail_action = NULL;
	hook_output->progname = NULL;
	hook_output->argv = NULL;
	hook_output->env = NULL;
	hook_output->vnl = NULL;
	hook_output->vnl_fail = NULL;
}
